package ch.poole.openinghoursfragment;

import ch.poole.openinghoursfragment.menu.Menu;
import ch.poole.openinghoursfragment.menu.MenuItem;
import ch.poole.openinghoursfragment.menu.MenuUtils;
import ch.poole.openinghoursfragment.menu.MeunItemType;
import ch.poole.openinghoursfragment.pickers.*;
import ch.poole.openinghoursfragment.templates.*;
import ch.poole.openinghoursparser.*;
import ch.poole.openinghoursparser.Holiday.Type;
import ch.poole.openinghoursparser.RuleModifier.Modifier;
import ch.poole.rangebar.RangeBar;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.accessibility.ability.AccessibleAbility;
import ohos.agp.colors.RgbColor;
import ohos.agp.colors.RgbPalette;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.window.dialog.PopupDialog;
import ohos.agp.window.service.Window;
import ohos.agp.window.service.WindowManager;
import ohos.app.Context;
import ohos.data.rdb.RdbStore;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.NotExistException;
import ohos.global.resource.Resource;
import ohos.global.resource.WrongTypeException;
import org.angmarch.views.NiceSpinner;
import org.angmarch.views.OnSpinnerItemSelectedListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * DialogFragment that implements an editor for OpenStreetMap opening_hours tags
 *
 * @author Simon Poole
 */
public class OpeningHoursFragment extends AbilitySlice implements SetRangeListener, SetDateRangeListener, SetTimeRangeListener, UpdateTextListener {
    private static final String DEBUG_TAG = OpeningHoursFragment.class.getSimpleName();

    private static final String VALUE_KEY = "value";
    private static final String ORIGINAL_VALUE_KEY = "original_value";
    private static final String KEY_KEY = "key";
    private static final String REGION_KEY = "region";
    private static final String OBJECT_KEY = "object";
    private static final String STYLE_KEY = "style";
    private static final String RULE_KEY = "rule";
    private static final String SHOWTEMPLATES_KEY = "show_templates";
    private static final String TEXTVALUES_KEY = "text_values";
    private static final String FRAGMENT_KEY = "fragment";
    private static final String LOCALE_KEY = "locale";

    protected static final int OSM_MAX_TAG_LENGTH = 255;

    private Context context = null;


    /**
     * Saved state
     */
    private ValueWithDescription key;
    private String region;
    private String object;
    private String openingHoursValue;
    private String originalOpeningHoursValue;
    private int styleRes = 0;
    private Locale locale;

    /**
     * If true we use a call back to the parent fragment
     */
    private boolean useFragmentCallback;

    private List<Rule> rules;

    private TextField text;
    private DirectionalLayout errorMessages;

    private OnSaveListener saveListener = null;

    List<String> weekDays = WeekDay.nameValues();

    List<String> months = Month.nameValues();

    private RdbStore mDatabase;

    private boolean loadedDefault = false;

    private boolean showTemplates = false;

    private ArrayList<ValueWithDescription> textValues;

    private OhTextWatcher watcher;
    private TextTextWatcher textWatcher;
    //
    private Button saveButton;
    //
    private Component headerLine;

    /**
     * True if we encountered a parse error
     */
    private boolean parseErrorFound;

    /**
     * record if we are not actually adding a OH value
     */
    private boolean textMode = false;
    private Intent intent = null;
    private final EventHandler eventHandler = new EventHandler(EventRunner.getMainEventRunner());

    static RangeBar.PinTextFormatter extendedTimeFormater = value -> {
        int minutes = Integer.parseInt(value);
        int tempMinutes = minutes - TimeSpan.MAX_TIME;
        return String.format(Locale.US, "%02d", tempMinutes / 60) + ":" + String.format(Locale.US, "%02d", minutes % 60);
    };

    static RangeBar.PinTextFormatter timeFormater = value -> {
        int minutes = Integer.parseInt(value);
        return String.format(Locale.US, "%02d", minutes / 60) + ":" + String.format(Locale.US, "%02d", minutes % 60);
    };
    private RadioContainer modeGroup;

    /**
     * Create a new OpeningHoursFragment with callback to an abilitySlice
     *
     * @param key   the key the OH values belongs to
     * @param value the OH value
     * @param style resource id
     * @param rule  rule to scroll to or -1 (currently ignored)
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstance(@NotNull String key, @NotNull String value, int style, int rule) {
        return newInstance(key, value, style, rule, false);
    }

    /**
     * Create a new OpeningHoursFragment with callback to an abilitySlice
     *
     * @param key           the key the OH values belongs to
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstance(@NotNull String key, @NotNull String value, int style, int rule, boolean showTemplates) {
        return newInstance(new ValueWithDescription(key, null), null, null, value, style, rule, showTemplates, null, null);
    }

    /**
     * Create a new OpeningHoursFragment with callback to an abilitySlice
     *
     * @param key           the key the OH values belongs to in an ValueWithDescription object
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @param textValues    for tags that can contain both OH and other values a list of possible non-OH values, or null
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstance(@NotNull ValueWithDescription key, @NotNull String value, int style, int rule, boolean showTemplates,
                                                   @Nullable ArrayList<ValueWithDescription> textValues) {
        return newInstance(key, null, null, value, style, rule, showTemplates, textValues, null);
    }

    /**
     * Create a new OpeningHoursFragment with callback to an abilitySlice
     *
     * @param key           the key the OH values belongs to in an ValueWithDescription object
     * @param region        the current region
     * @param object        the object in question (typically the main osm tag)
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @param textValues    for tags that can contain both OH and other values a list of possible non-OH values, or null
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstance(@NotNull ValueWithDescription key, String region, String object, @NotNull String value, int style, int rule,
                                                   boolean showTemplates, @Nullable ArrayList<ValueWithDescription> textValues) {
        return newInstance(key, region, object, value, style, rule, showTemplates, textValues, null);
    }

    /**
     * Create a new OpeningHoursFragment with callback to an abilitySlice
     *
     * @param key           the key the OH values belongs to in an ValueWithDescription object
     * @param region        the current region
     * @param object        the object in question (typically the main osm tag)
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @param textValues    for tags that can contain both OH and other values a list of possible non-OH values, or null
     * @param locale        if not null use a different Locale than the default for parser error messages
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstance(@NotNull ValueWithDescription key, String region, String object, @NotNull String value, int style, int rule,
                                                   boolean showTemplates, @Nullable ArrayList<ValueWithDescription> textValues, @Nullable Locale locale) {
        OpeningHoursFragment f = new OpeningHoursFragment();
        Intent paramIntent = f.getParamIntent(key, region, object, value, style, rule, showTemplates, textValues, locale);
        f.setIntent(paramIntent);
        return f;
    }


    /**
     * Create a new OpeningHoursFragment with callback to a fragment
     *
     * @param key   the key the OH values belongs to
     * @param value the OH value
     * @param style resource id
     * @param rule  rule to scroll to or -1 (currently ignored)
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstanceForFragment(@NotNull String key, @NotNull String value, int style, int rule) {
        return newInstanceForFragment(key, value, style, rule, false);
    }

    /**
     * Create a new OpeningHoursFragment with callback to a fragment
     *
     * @param key           the key the OH values belongs to
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstanceForFragment(@NotNull String key, @NotNull String value, int style, int rule, boolean showTemplates) {
        return newInstanceForFragment(new ValueWithDescription(key, null), null, null, value, style, rule, showTemplates, null, null);
    }

    /**
     * Create a new OpeningHoursFragment with callback to a fragment
     *
     * @param key           the key the OH values belongs to in an ValueWithDescription object
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @param textValues    for tags that can contain both OH and other values a list of possible non-OH values, or null
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstanceForFragment(@NotNull ValueWithDescription key, @NotNull String value, int style, int rule,
                                                              boolean showTemplates, @Nullable ArrayList<ValueWithDescription> textValues) {
        return newInstanceForFragment(key, null, null, value, style, rule, showTemplates, textValues, null);
    }

    /**
     * Create a new OpeningHoursFragment with callback to a fragment
     *
     * @param key           the key the OH values belongs to in an ValueWithDescription object
     * @param region        the current region
     * @param object        the object in question (typically the main osm tag)
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @param textValues    for tags that can contain both OH and other values a list of possible non-OH values, or null
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstanceForFragment(@NotNull ValueWithDescription key, String region, String object, @NotNull String value, int style,
                                                              int rule, boolean showTemplates, @Nullable ArrayList<ValueWithDescription> textValues) {
        return newInstanceForFragment(key, region, object, value, style, rule, showTemplates, textValues, null);
    }

    /**
     * Create a new OpeningHoursFragment with callback to a fragment
     *
     * @param key           the key the OH values belongs to in an ValueWithDescription object
     * @param region        the current region
     * @param object        the object in question (typically the main osm tag)
     * @param value         the OH value
     * @param style         resource id
     * @param rule          rule to scroll to or -1 (currently ignored)
     * @param showTemplates if value is empty show the template selector instead of using a default when true
     * @param textValues    for tags that can contain both OH and other values a list of possible non-OH values, or null
     * @param locale        if not null use a different Locale than the default for parser error messages
     * @return an OpeningHoursFragment
     */
    public static OpeningHoursFragment newInstanceForFragment(@NotNull ValueWithDescription key, @Nullable String region, @Nullable String object,
                                                              @NotNull String value, int style, int rule, boolean showTemplates, @Nullable ArrayList<ValueWithDescription> textValues, @Nullable Locale locale) {
        OpeningHoursFragment f = new OpeningHoursFragment();
        Intent paramIntent = f.getParamIntent(key, region, object, value, style, rule, showTemplates, textValues, locale);
        f.setIntent(paramIntent);
        return f;
    }

    public Intent getIntent() {
        return intent;
    }

    public void setIntent(Intent intent) {
        this.intent = intent;
    }

    public Intent getParamIntent(@NotNull ValueWithDescription key, String region, String object, @NotNull String value, int style, int rule,
                                 boolean showTemplates, @Nullable ArrayList<ValueWithDescription> textValues, @Nullable Locale locale) {
        Intent intent = new Intent();
        intent.setParam(KEY_KEY, key);
        intent.setParam(REGION_KEY, region);
        intent.setParam(OBJECT_KEY, object);
        intent.setParam(VALUE_KEY, value);
        intent.setParam(ORIGINAL_VALUE_KEY, value);
        intent.setParam(STYLE_KEY, style);
        intent.setParam(RULE_KEY, rule);
        intent.setParam(SHOWTEMPLATES_KEY, showTemplates);
        intent.setParam(FRAGMENT_KEY, true);
        intent.setSequenceableArrayListParam(TEXTVALUES_KEY, textValues);
        intent.setParam(LOCALE_KEY, locale);
        return intent;
    }

    @Override
    protected void onStart(Intent intent) {
        try {
            int color = getResourceManager().getElement(ResourceTable.Color_black).getColor();
            getWindow().setStatusBarColor(color);
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
        super.onStart(intent);

        mDatabase = new TemplateDatabaseHelper(getContext()).getDb();
        int initialRule = -1;
        key = intent.getSequenceableParam(KEY_KEY);
        region = intent.getStringParam(REGION_KEY);
        object = intent.getStringParam(OBJECT_KEY);
        openingHoursValue = intent.getStringParam(VALUE_KEY);
        originalOpeningHoursValue = intent.getStringParam(ORIGINAL_VALUE_KEY);
        styleRes = intent.getIntParam(STYLE_KEY, 0);
        useFragmentCallback = intent.getBooleanParam(FRAGMENT_KEY, false);
        textValues = intent.getSequenceableArrayListParam(TEXTVALUES_KEY);
        initialRule = intent.getIntParam(REGION_KEY, -1);
        showTemplates = intent.getBooleanParam(SHOWTEMPLATES_KEY, false);
        locale = intent.getSequenceableParam(LOCALE_KEY);
        if (openingHoursValue == null || "".equals(openingHoursValue)) {
            if (!showTemplates) {
                loadDefault();
                loadedDefault = openingHoursValue != null;
            }
        }
        super.setUIContent(ResourceTable.Layout_openinghours);
        context = getContext();
        ScrollView sv = (ScrollView) findComponentById(ResourceTable.Id_openinghours_view);
        watcher = new OhTextWatcher(sv);
        textWatcher = new TextTextWatcher();
        // set parser locale singleton
        I18n.setLocale(locale != null ? locale : Locale.getDefault());
        DirectionalLayout modeContainer = (DirectionalLayout) findComponentById(ResourceTable.Id_modeContainer);
        headerLine = findComponentById(ResourceTable.Id_headerLine);
        errorMessages = (DirectionalLayout) findComponentById(ResourceTable.Id_openinghours_error_messages);
        if (textValues != null) {
            modeGroup = (RadioContainer) findComponentById(ResourceTable.Id_modeGroup);
            RadioButton useOH = (RadioButton) findComponentById(ResourceTable.Id_use_oh);
            RadioButton useText = (RadioButton) findComponentById(ResourceTable.Id_use_text);
            if (textValues.contains(new ValueWithDescription(openingHoursValue, null)) || openingHoursValue == null || "".equals(openingHoursValue)) {
                useText.setChecked(true);
                textMode = true;
                headerLine.setVisibility(Component.VISIBLE);
            } else {
                useOH.setChecked(true);
                headerLine.setVisibility(Component.HIDE);
            }

            modeGroup.setMarkChangedListener(new RadioContainer.CheckedStateChangedListener() {
                @Override
                public void onCheckedChanged(RadioContainer radioContainer, int i) {
                    Logger.getLogger("TAG").log(Level.INFO, "走了没走了没!!!");
                    openingHoursValue = text.getText();
                    text.removeTextObserver(watcher);
                    text.removeTextObserver(textWatcher);
                    eventHandler.removeAllEvent();
                    removeHighlight(text);
                    errorMessages.removeAllComponents();
                    buildLayout(openingHoursValue, -1);
                }
            });
        } else {
            modeContainer.setVisibility(Component.HIDE);
            headerLine.setVisibility(Component.HIDE);
        }
        buildLayout(openingHoursValue == null ? "" : openingHoursValue, initialRule);
        Button cancel = (Button) findComponentById(ResourceTable.Id_cancel);
        cancel.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                terminate();
            }
        });
        saveButton = (Button) findComponentById(ResourceTable.Id_save);
        enableSaveButton(openingHoursValue);
        saveButton.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                saveListener.save(key.getValue(), text.getText());

                if (modeGroup != null) {
                    modeGroup.setMarkChangedListener(null);
                }
                terminate();
            }
        });
    }

    /**
     * Try to locate a reasonable default value
     */
    private void loadDefault() {
        String[][] values = new String[][]{{region, object}, {null, object}, {region, null}};
        for (String[] v : values) {
            openingHoursValue = TemplateDatabase.getDefault(mDatabase, key.getValue(), region, object);
            if (openingHoursValue != null) {
                return;
            }
        }
        openingHoursValue = TemplateDatabase.getDefault(mDatabase, null, null, null);
    }

    public void setOnSaveListener(OnSaveListener onSaveListener) {
        this.saveListener = onSaveListener;
    }

    public OnSaveListener getOnSaveListener() {
        return saveListener;
    }


    /**
     * Enable the save button if the text has changed
     *
     * @author simon
     */
    private class TextTextWatcher implements Text.TextObserver {

        @Override
        public void onTextUpdated(String s, int i, int i1, int i2) {
            enableSaveButton(text.getText().toString());
        }
    }

    /**
     * Re-parses and rebuilds the form if the text is changed by typing
     *
     * @author simon
     */
    private class OhTextWatcher implements Text.TextObserver {
        final ScrollView scrollView;

        /**
         * Construct a new instance
         *
         * @param scrollView the ScrollView holding the bits that we will want to update
         */
        OhTextWatcher(@NotNull ScrollView scrollView) {
            this.scrollView = scrollView;
        }

        @Override
        public void onTextUpdated(String s, int i, int i1, int i2) {
            Runnable rebuild = () -> {
                String gggg = text.getText();
                Logger.getLogger("TAG").log(Level.INFO, "获取到的规则为:" + gggg);
                OpeningHoursParser parser = new OpeningHoursParser(new ByteArrayInputStream(OpeningHoursFragment.this.text.getText().toString().getBytes()));
                try {
                    rules = parser.rules(false);
                    buildForm(scrollView, rules);
                    removeHighlight(OpeningHoursFragment.this.text);
                    errorMessages.removeAllComponents();
                } catch (OpeningHoursParseException pex) {
                    LogUtil.debug(pex.getMessage());
                    highlightParseError(OpeningHoursFragment.this.text, pex);
                    errorMessages.removeAllComponents();
                    for (OpeningHoursParseException ex : pex.getExceptions()) {
                        Text message = new Text(getContext());
                        message.setMaxTextLines(1);
                        message.setText(ex.getMessage());
                        message.setTextSize(14, Text.TextSizeType.FP);
                        message.setTextColor(new Color(Color.getIntColor("#E87850")));
                        final int column = ex.getColumn() + 1;
                        message.setClickedListener(new Component.ClickedListener() {
                            @Override
                            public void onClick(Component component) {
                            }
                        });
                        errorMessages.addComponent(message);
                    }
                } catch (TokenMgrError err) {
                    // we currently can't do anything reasonable here except ignore
                    LogUtil.error(err.getMessage());
                }
                enableSaveButton(OpeningHoursFragment.this.text.getText().toString());
            };
            eventHandler.removeTask(rebuild);
            if (s != null) {
                eventHandler.postTask(rebuild, 500);
            } else {
                eventHandler.postTask(rebuild, 100);
            }
        }
    }

    private Component.ClickedListener autocompleteOnClick = v -> {
        if (v.hasFocus()) {
            showSoftInput();
        }
    };

    public static boolean showSoftInput() {
        try {
            Class inputClass = Class.forName("ohos.miscservices.inputmethod.InputMethodController");
            Method method = inputClass.getMethod("getInstance");
            Object object = method.invoke(new Object[]{});
            Method startInput = inputClass.getMethod("startInput", int.class, boolean.class);
            return (boolean) startInput.invoke(object, 1, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private void showDialog(Component component) {
        PopupDialog popupDialog = new PopupDialog(getContext(), component, 700, -2);
        DirectionalLayout directionalLayout = new DirectionalLayout(context);
        ComponentContainer.LayoutConfig layoutConfig = directionalLayout.getLayoutConfig();
        layoutConfig.width = ComponentContainer.LayoutConfig.MATCH_PARENT;
        layoutConfig.height = ComponentContainer.LayoutConfig.MATCH_CONTENT;
        directionalLayout.setLayoutConfig(layoutConfig);
        ShapeElement shapeElement = new ShapeElement(context, ResourceTable.Graphic_shape_line);
        directionalLayout.setBackground(shapeElement);


        for (int i = 0; i < textValues.size(); i++) {
            String value = textValues.get(i).getValue();
            String description = textValues.get(i).getDescription();
            Text text = new Text(getContext());
            text.setText(description);
            text.setMarginLeft(AttrHelper.vp2px(10,context));
            text.setPaddingTop(AttrHelper.vp2px(5,context));
            text.setPaddingBottom(AttrHelper.vp2px(5,context));
            text.setMultipleLine(true);
            text.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
            text.setTextSize(15, Text.TextSizeType.FP);
            text.setTextColor(Color.BLACK);
            text.setBackground(new ShapeElement() {{
                setRgbColor(RgbPalette.TRANSPARENT);
            }});
            text.setClickedListener(new Component.ClickedListener() {
                @Override
                public void onClick(Component component) {
                    OpeningHoursFragment.this.text.setText(value);
                    popupDialog.hide();
                }
            });
            directionalLayout.addComponent(text);
        }

        popupDialog.setCustomComponent(directionalLayout);
        popupDialog.setSize(MenuUtils.getWindowWidth(context)-AttrHelper.vp2px(40,context), ComponentContainer.LayoutConfig.MATCH_CONTENT);
        popupDialog.setBackColor(Color.TRANSPARENT);
        popupDialog.setArrowSize(50, 30);
        popupDialog.setArrowOffset(100);
        popupDialog.setHasArrow(true);
        popupDialog.setAutoClosable(true);
        popupDialog.show();
    }

    /**
     * Build the parts of the layout that only need to be done once
     *
     * @param openingHoursValue the OH value
     * @param initialRule       index of the rule to scroll to, currently ignored
     * @return a ScrollView
     */
    private ScrollView buildLayout(@NotNull String openingHoursValue, final int initialRule) {
        text = (TextField) findComponentById(ResourceTable.Id_openinghours_string_edit);
        String keyDescription = key.getDescription();

        if (keyDescription != null && !"".equals(keyDescription)) {
            text.setHint(keyDescription);
        } else {
            try {
                String string = getResourceManager().getElement(ResourceTable.String_default_hint).getString();
                text.setHint(string);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NotExistException e) {
                e.printStackTrace();
            } catch (WrongTypeException e) {
                e.printStackTrace();
            }
        }
        final ScrollView sv = (ScrollView) findComponentById(ResourceTable.Id_openinghours_view);
        if (text != null && sv != null) {
            sv.removeAllComponents();
            DirectionalLayout fab = (DirectionalLayout) findComponentById(ResourceTable.Id_more);
            // non-OH support
            if (textValues != null) {
                RadioContainer modeGroup = (RadioContainer) findComponentById(ResourceTable.Id_modeGroup);
                final RadioButton useText = (RadioButton) findComponentById(ResourceTable.Id_use_text);
                if (useText.isChecked()) {
                    text.removeTextObserver(watcher);
                    eventHandler.removeAllEvent();
                    text.setDoubleClickedListener(new Component.DoubleClickedListener() {
                        @Override
                        public void onDoubleClick(Component component) {
                            showDialog(text);
                        }
                    });
                    text.setClickedListener(autocompleteOnClick);


                    text.setText(openingHoursValue);
                    text.removeTextObserver(textWatcher);
                    text.addTextObserver(textWatcher);
                    fab.setVisibility(Component.HIDE);
                    headerLine.setVisibility(Component.VISIBLE);
                    textMode = true;
                    return sv;
                } else {
                    text.setDoubleClickedListener(null);
                    text.setClickedListener(null);
                    textMode = false;
                    if ("".equals(openingHoursValue)) {
                        if (!showTemplates) {
                            loadDefault();
                            if (!"".equals(openingHoursValue)) {
                                try {

                                    Util.toastTop(context, context.getResourceManager().getElement(ResourceTable.String_loaded_default).getString());
                                } catch (IOException e) {
                                    e.printStackTrace();
                                } catch (NotExistException e) {
                                    e.printStackTrace();
                                } catch (WrongTypeException e) {
                                    e.printStackTrace();
                                }

                            }
                        } else {
                            showTemplates = false;
                            showTemplateMangementDialog(false, key, null, null, text.getText());
                        }
                    }
                    headerLine.setVisibility(Component.HIDE);
                }
            }
            text.setText(openingHoursValue);
            text.removeTextObserver(textWatcher);
            text.removeTextObserver(watcher);
            text.addTextObserver(watcher);
            fab.setVisibility(Component.VISIBLE);

            OpeningHoursParser parser = new OpeningHoursParser(new ByteArrayInputStream(openingHoursValue.getBytes()));
            try {
                rules = parser.rules(false);
                buildForm(sv, rules);
                removeHighlight(text);
            } catch (OpeningHoursParseException pex) {
                LogUtil.debug(pex.getMessage());
                highlightParseError(text, pex);
            } catch (TokenMgrError err) {
                // we currently can't do anything reasonable here except ignore
                LogUtil.error(err.getMessage());
            }

            class AddRuleListener implements OnMenuItemClickListener {
                String ruleString;

                /**
                 * Construct a new listener for creating Rules
                 *
                 * @param rule a String containing a rule
                 */
                AddRuleListener(@NotNull String rule) {
                    ruleString = rule;
                }

                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    OpeningHoursParser parser = new OpeningHoursParser(new ByteArrayInputStream(ruleString.getBytes()));
                    List<Rule> rules2 = null;
                    try {
                        rules2 = parser.rules(false);
                    } catch (ParseException pex) {
                        LogUtil.error(pex.getMessage());
                    } catch (TokenMgrError err) {
                        LogUtil.error(err.getMessage());
                    }
                    if (rules2 != null && !rules2.isEmpty()) {
                        if (rules == null || hasParseError()) { // if there was an unparseable string it needs to be

                            try {
                                Util.toastTop(context, context.getResourceManager().getElement(ResourceTable.String_would_overwrite_invalid_value).getString());
                                return true;
                            } catch (IOException e) {
                                e.printStackTrace();
                            } catch (NotExistException e) {
                                e.printStackTrace();
                            } catch (WrongTypeException e) {
                                e.printStackTrace();
                            }
                        }
                        rules.add(rules2.get(0));
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                        // scroll to bottom
                        new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                            @Override
                            public void run() {
                                Util.scrollToRow(sv, null, false, false);
                            }
                        }, 200);
                    }
                    return true;
                }
            }
            fab.setClickedListener(new Component.ClickedListener() {
                @Override
                public void onClick(Component component) {

                    Menu popup = new Menu(context, fab);
                    MenuItem clear = popup.add(MeunItemType.NO_SUB, ResourceTable.String_clear);
                    clear.setOnMenuItemClickListener(item -> {
                        if (rules != null) { // FIXME should likely disable the entry if there is actually nothing to clear
                            rules.clear();
                            updateString();
                            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                        } else {
                            text.setText("");
                            watcher.onTextUpdated(null, 0, 0, 0);
                        }
                        return true;
                    });
                    // menu items for adding rules
                    MenuItem addRule = popup.add(MeunItemType.NO_SUB, ResourceTable.String_add_rule);
                    addRule.setOnMenuItemClickListener(new AddRuleListener("Mo 6:00-20:00"));
                    MenuItem addRulePH = popup.add(MeunItemType.NO_SUB, ResourceTable.String_add_rule_closed_on_holidays);
                    addRulePH.setOnMenuItemClickListener(new AddRuleListener("PH closed"));
                    MenuItem addRule247 = popup.add(MeunItemType.NO_SUB, ResourceTable.String_add_rule_247);
                    addRule247.setOnMenuItemClickListener(new AddRuleListener("24/7"));

                    MenuItem loadTemplate = popup.add(MeunItemType.NO_SUB, ResourceTable.String_load_template);
                    loadTemplate.setOnMenuItemClickListener(item -> {
                        showTemplateMangementDialog(false, key, region, object, text.getText());
                        return true;
                    });
                    MenuItem saveTemplate = popup.add(MeunItemType.NO_SUB, ResourceTable.String_save_to_template);
                    saveTemplate.setOnMenuItemClickListener(item -> {
                        TemplateDialog templateMangementDialog = new TemplateDialog(OpeningHoursFragment.this);
                        TemplateDialog.newInstance(text.getText().toString(), key, false, -1, context);
                        present(templateMangementDialog, new Intent());
                        return true;
                    });
                    MenuItem manageTemplate = popup.add(MeunItemType.NO_SUB, ResourceTable.String_manage_templates);
                    manageTemplate.setOnMenuItemClickListener(item -> {
                        showTemplateMangementDialog(true, key, region, object, text.getText());
                        return true;
                    });
                    MenuItem refresh = popup.add(MeunItemType.NO_SUB, ResourceTable.String_refresh);
                    refresh.setOnMenuItemClickListener(item -> {
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                        return true;
                    });

                    popup.showPop();// showing popup menu
                }
            });
        }
        return sv;
    }

    /**
     * Check if we have an parser error
     *
     * @return true if there was a parser error
     */
    public boolean hasParseError() {
        return parseErrorFound;
    }

    /**
     * Highlight the position of a parse error
     * <p>
     * Side effect sets parserErrorFound to true
     *
     * @param text  he TextField the string is displayed in
     * @param ohpex the ParseException to use
     */
    private void highlightParseError(@NotNull TextField text, @NotNull OpeningHoursParseException ohpex) {
        parseErrorFound = true;

        text.removeTextObserver(watcher); // avoid infinite loop
        text.addTextObserver(watcher);
    }

    /**
     * Remove all parse error highlighting
     * <p>
     * Side effect sets parserErrorFound to false
     *
     * @param text the TextField the string is displayed in
     */
    private void removeHighlight(@NotNull TextField text) {
        parseErrorFound = false;
        text.removeTextObserver(watcher);
        if (rules != null) {
            String t = ch.poole.openinghoursparser.Util.rulesToOpeningHoursString(rules);
            text.setText(t);
        }
        text.addTextObserver(watcher);
    }

    /**
     * (Re-)Build the contents of the scroll view
     *
     * @param sv    the ScrollView
     * @param rules List of Rules to display
     */
    private synchronized void buildForm(@NotNull ScrollView sv, @NotNull List<Rule> rules) {
        sv.removeAllComponents();
        DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(DirectionalLayout.LayoutConfig.MATCH_PARENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT);
        DirectionalLayout ll = new DirectionalLayout(getContext());
        ll.setLayoutConfig(layoutConfig);
        ll.setPadding(0, 0, 0, vpToPixels(64));
        ll.setOrientation(Component.VERTICAL);
        sv.addComponent(ll);
        addRules(false, rules, ll);
    }

    /**
     * Loop over the list of rules adding views and menus for the entries
     *
     * @param groupMode use groupMode, currently ignored
     * @param rules     List of Rules to display
     * @param ll        layout to add the wules to
     */
    private void addRules(boolean groupMode, @NotNull final List<Rule> rules, @NotNull DirectionalLayout ll) {
        boolean first = true;
        int headerCount = 1;
        for (final Rule r : rules) {
            if (first) { // everything except days and times should be
                // the same and only needs to be displayed
                // once in groupMode, in normal mode this is
                // always true
                DirectionalLayout groupHeader = (DirectionalLayout) LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_rule_header, null, false);
                Text header = (Text) groupHeader.findComponentById(ResourceTable.Id_header);
                try {
                    header.setText(groupMode ? String.format(context.getResourceManager().getElement(ResourceTable.String_group_header).getString(), headerCount) : String.format(context.getResourceManager().getElement(ResourceTable.String_rule_header).getString(), headerCount));
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }
                RadioButton normal = (RadioButton) groupHeader.findComponentById(ResourceTable.Id_normal_rule);
                if (!r.isAdditive() && !r.isFallBack()) {
                    normal.setChecked(true);
                }
                normal.setClickedListener(new Component.ClickedListener() {
                    @Override
                    public void onClick(Component component) {
                        RadioButton rb = (RadioButton) component;
                        if (!rb.isChecked()) {
                            r.setFallBack(false);
                            r.setAdditive(false);
                            updateString();
                            watcher.onTextUpdated(null, 0, 0, 0);
                        }
                    }
                });

                RadioButton additive = (RadioButton) groupHeader.findComponentById(ResourceTable.Id_additive_rule);
                if (r.isAdditive()) {
                    additive.setChecked(true);
                }
                additive.setClickedListener(new Component.ClickedListener() {
                    @Override
                    public void onClick(Component component) {
                        RadioButton rb = (RadioButton) component;
                        if (!rb.isChecked()) {
                            r.setFallBack(false);
                            r.setAdditive(true);
                            updateString();
                            watcher.onTextUpdated(null, 0, 0, 0);
                        }
                    }
                });

                RadioButton fallback = (RadioButton) groupHeader.findComponentById(ResourceTable.Id_fallback_rule);
                if (r.isFallBack()) {
                    fallback.setChecked(true);
                }
                fallback.setClickedListener(new Component.ClickedListener() {
                    @Override
                    public void onClick(Component component) {
                        RadioButton rb = (RadioButton) component;
                            r.setFallBack(true);
                            r.setAdditive(false);
                            rules.remove(r); // move to last position
                            rules.add(r);
                            updateString();
                            watcher.onTextUpdated(null, 0, 0, 0);
                    }
                });
                // add menu items for rules
                Menu menu = addStandardMenuItems(groupHeader, () -> {
                    rules.remove(r);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                });

                if (r.getModifier() == null) {
                    MenuItem addModifierAndComment = menu.add(MeunItemType.NO_SUB, ResourceTable.String_add_modifier_comment);
                    addModifierAndComment.setOnMenuItemClickListener(item -> {
                        RuleModifier modifier = new RuleModifier();
                        modifier.setModifier(Modifier.CLOSED);
                        r.setModifier(modifier);
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0);
                        return true;
                    });
                }

                MenuItem addHoliday = menu.add(MeunItemType.NO_SUB, ResourceTable.String_add_holiday);
                addHoliday.setOnMenuItemClickListener(item -> {
                    List<Holiday> holidays = r.getHolidays();
                    if (holidays == null) {
                        r.setHolidays(new ArrayList<>());
                        holidays = r.getHolidays();
                    }
                    Holiday holiday = new Holiday();
                    holiday.setType(Type.PH);
                    holidays.add(holiday);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });
                MenuItem timespanMenu = menu.add(MeunItemType.HAS_SUB, ResourceTable.String_timespan_menu);
                timespanMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        item.showPop();
                        return true;
                    }
                });
                timespanMenu.addTopNoClick(ResourceTable.String_timespan_menu);
                class SetTimeSpanListener implements OnMenuItemClickListener {
                    private final int start;
                    private final Event startEvent;
                    private final int end;
                    private final Event endEvent;

                    /**
                     * Create a listener that sets the time
                     *
                     * @param start start time
                     * @param startEvent start event or null
                     * @param end end time
                     * @param endEvent end event or null
                     */
                    SetTimeSpanListener(int start, @Nullable Event startEvent, int end, @Nullable Event endEvent) {
                        this.start = start;
                        this.startEvent = startEvent;
                        this.end = end;
                        this.endEvent = endEvent;
                    }

                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        List<TimeSpan> ts = r.getTimes();
                        if (ts == null) {
                            r.setTimes(new ArrayList<>());
                            ts = r.getTimes();
                        }
                        TimeSpan t = new TimeSpan();
                        if (startEvent == null) {
                            t.setStart(start);
                        } else {
                            VariableTime vt = new VariableTime();
                            vt.setEvent(startEvent);
                            t.setStartEvent(vt);
                        }
                        if (endEvent == null && end > 0) {
                            t.setEnd(end);
                        } else if (endEvent != null) {
                            VariableTime vt = new VariableTime();
                            vt.setEvent(endEvent);
                            t.setEndEvent(vt);
                        }
                        ts.add(t);
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0);
                        return true;
                    }
                }
                MenuItem addTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_time_time);
                addTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(360, null, 1440, null));

                MenuItem addExtendedTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_time_extended_time);
                addExtendedTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(0, null, 2880, null));

                MenuItem addVarTimeTimeTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_time_time);
                addVarTimeTimeTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(0, Event.DAWN, 1440, null));

                MenuItem addVarTimeExtendedTimeTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_time_extended_time);
                addVarTimeExtendedTimeTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(0, Event.DAWN, 2880, null));

                MenuItem addTimeVarTimeTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_time_variable_time);
                addTimeVarTimeTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(360, null, 2880, Event.DUSK));

                MenuItem addVarTimeVarTimeTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_time_variable_time);
                addVarTimeVarTimeTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(360, Event.DAWN, 2880, Event.DUSK));

                MenuItem addTimeTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_time);
                addTimeTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(360, null, -1, null));

                MenuItem addTimeOpenEndTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_time_open_end);
                addTimeOpenEndTimespan.setOnMenuItemClickListener(item -> {
                    List<TimeSpan> ts = r.getTimes();
                    if (ts == null) {
                        r.setTimes(new ArrayList<>());
                        ts = r.getTimes();
                    }
                    TimeSpan t = new TimeSpan();
                    t.setStart(360);
                    t.setOpenEnded(true);
                    ts.add(t);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });

                MenuItem addVarTimeTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_time);
                addVarTimeTimespan.setOnMenuItemClickListener(new SetTimeSpanListener(360, Event.DAWN, -1, null));

                MenuItem addVarTimeOpenEndTimespan = timespanMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_time_open_end);
                addVarTimeOpenEndTimespan.setOnMenuItemClickListener(item -> {
                    List<TimeSpan> ts = r.getTimes();
                    if (ts == null) {
                        r.setTimes(new ArrayList<>());
                        ts = r.getTimes();
                    }
                    TimeSpan t = new TimeSpan();
                    VariableTime startVt = new VariableTime();
                    startVt.setEvent(Event.DAWN);
                    t.setStartEvent(startVt);
                    t.setOpenEnded(true);
                    ts.add(t);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });

                MenuItem addWeekdayRange = menu.add(MeunItemType.NO_SUB, ResourceTable.String_week_day_range);
                addWeekdayRange.setOnMenuItemClickListener(item -> {
                    List<WeekDayRange> wd = r.getDays();
                    if (wd == null) {
                        r.setDays(new ArrayList<>());
                        wd = r.getDays();
                    }
                    WeekDayRange d = new WeekDayRange();
                    if (wd.isEmpty()) {
                        d.setStartDay("Mo");
                        // d.setEndDay("Su"); a single day is better from an UI pov
                    } else {
                        // add a single day with nth
                        d.setStartDay("Mo");
                        List<Nth> nths = new ArrayList<>();
                        Nth nth = new Nth();
                        nth.setStartNth(1);
                        nths.add(nth);
                        d.setNths(nths);
                    }
                    wd.add(d);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });

                MenuItem dateRangeMenu = menu.add(MeunItemType.HAS_SUB, ResourceTable.String_daterange_menu);
                dateRangeMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        item.showPop();
                        return true;
                    }
                });
                dateRangeMenu.addTopNoClick(ResourceTable.String_daterange_menu);
                MenuItem addDateDateRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_date);
                addDateDateRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset dwo = new DateWithOffset();
                    dwo.setMonth(Month.JAN);
                    dateRange.setStartDate(dwo);
                }));
                MenuItem addVarDateDateRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_date);
                addVarDateDateRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset dwo = new DateWithOffset();
                    dwo.setVarDate(VarDate.EASTER);
                    dateRange.setStartDate(dwo);
                }));
                MenuItem addDateVarDateRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_variable_date);
                addDateVarDateRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setVarDate(VarDate.EASTER);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addVarDateVarDateRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_variable_date);
                addVarDateVarDateRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setVarDate(VarDate.EASTER);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setVarDate(VarDate.EASTER);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addOccurrenceOccurrenceRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_occurrence);
                addOccurrenceOccurrenceRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.FEB);
                    endDwo.setNth(WeekDay.MO, 1);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addOccurrenceDateRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_date);
                addOccurrenceDateRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.JAN);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addDateOccurrenceRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_occurrence);
                addDateOccurrenceRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.FEB);
                    endDwo.setNth(WeekDay.MO, 1);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addOccurrenceVarDateRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_variable_date);
                addOccurrenceVarDateRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setVarDate(VarDate.EASTER);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addVarDateOccurrenceRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_occurrence);
                addVarDateOccurrenceRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setVarDate(VarDate.EASTER);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.JAN);
                    endDwo.setNth(WeekDay.MO, 1);
                    dateRange.setEndDate(endDwo);
                }));

                MenuItem addDateOpenEndRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_open_end);
                addDateOpenEndRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setOpenEnded(true);
                    dateRange.setStartDate(startDwo);
                }));
                MenuItem addVarDateOpenEndRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_open_end);
                addVarDateOpenEndRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setVarDate(VarDate.EASTER);
                    startDwo.setOpenEnded(true);
                    dateRange.setStartDate(startDwo);
                }));
                MenuItem addOccurrenceOpenEndRange = dateRangeMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_open_end);
                addOccurrenceOpenEndRange.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    startDwo.setOpenEnded(true);
                    dateRange.setStartDate(startDwo);
                }));

                // the same with offsets
                MenuItem dateRangeOffsetMenu = dateRangeMenu.add(MeunItemType.HAS_SUB, ResourceTable.String_with_offsets);
                dateRangeOffsetMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        item.showPop();
                        return true;
                    }
                });
                dateRangeOffsetMenu.addTopNoClick(ResourceTable.String_with_offsets);
                MenuItem addDateDateRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_date);
                addDateDateRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset dwo = new DateWithOffset();
                    dwo.setMonth(Month.JAN);
                    dwo.setDay(1);
                    dwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(dwo);
                }));
                MenuItem addVarDateDateRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_date);
                addVarDateDateRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset dwo = new DateWithOffset();
                    dwo.setVarDate(VarDate.EASTER);
                    dwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(dwo);
                }));
                MenuItem addDateVarDateRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_variable_date);
                addDateVarDateRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setDay(1);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setVarDate(VarDate.EASTER);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addVarDateVarDateRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_variable_date);
                addVarDateVarDateRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setVarDate(VarDate.EASTER);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setVarDate(VarDate.EASTER);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addOccurrenceOccurrenceRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_occurrence);
                addOccurrenceOccurrenceRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.FEB);
                    endDwo.setNth(WeekDay.MO, 1);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addOccurrenceDateRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_date);
                addOccurrenceDateRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.JAN);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addDateOccurrenceRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_occurrence);
                addDateOccurrenceRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.FEB);
                    endDwo.setNth(WeekDay.MO, 1);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addOccurrenceVarDateRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_variable_date);
                addOccurrenceVarDateRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setVarDate(VarDate.EASTER);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addVarDateOccurrenceRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_occurrence);
                addVarDateOccurrenceRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setVarDate(VarDate.EASTER);
                    dateRange.setStartDate(startDwo);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    DateWithOffset endDwo = new DateWithOffset();
                    endDwo.setMonth(Month.JAN);
                    endDwo.setNth(WeekDay.MO, 1);
                    dateRange.setEndDate(endDwo);
                }));
                MenuItem addDateOpenEndRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_date_open_end);
                addDateOpenEndRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setDay(1);
                    startDwo.setOpenEnded(true);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                }));
                MenuItem addVarDateOpenEndRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_variable_date_open_end);
                addVarDateOpenEndRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setVarDate(VarDate.EASTER);
                    startDwo.setOpenEnded(true);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                }));
                MenuItem addOccurrenceOpenEndRangeWithOffsets = dateRangeOffsetMenu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_open_end);
                addOccurrenceOpenEndRangeWithOffsets.setOnMenuItemClickListener(new DateRangeMenuListener(r, (DateRange dateRange) -> {
                    DateWithOffset startDwo = new DateWithOffset();
                    startDwo.setMonth(Month.JAN);
                    startDwo.setNth(WeekDay.MO, 1);
                    startDwo.setOpenEnded(true);
                    startDwo.setWeekDayOffset(WeekDay.MO);
                    dateRange.setStartDate(startDwo);
                }));

                MenuItem addYearRange = menu.add(MeunItemType.NO_SUB, ResourceTable.String_add_year_range);
                addYearRange.setOnMenuItemClickListener(item -> {
                    List<YearRange> years = r.getYears();
                    if (years == null) {
                        r.setYears(new ArrayList<>());
                        years = r.getYears();
                    }
                    YearRange yearRange = new YearRange();
                    yearRange.setStartYear(Calendar.getInstance().get(Calendar.YEAR));
                    years.add(yearRange);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });

                MenuItem addWeekRange = menu.add(MeunItemType.NO_SUB, ResourceTable.String_add_week_range);
                addWeekRange.setOnMenuItemClickListener(item -> {
                    List<WeekRange> weeks = r.getWeeks();
                    if (weeks == null) {
                        r.setWeeks(new ArrayList<>());
                        weeks = r.getWeeks();
                    }
                    WeekRange weekRange = new WeekRange();
                    weekRange.setStartWeek(1);
                    weeks.add(weekRange);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });

                MenuItem duplicateRule = menu.add(MeunItemType.NO_SUB, ResourceTable.String_duplicate_rule);
                duplicateRule.setOnMenuItemClickListener(item -> {
                    Rule duplicate = r.copy();
                    int current = rules.indexOf(r);
                    if (current < 0) { // not found shouldn't happen
                        return true;
                    }
                    rules.add(Math.max(0, current + 1), duplicate);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                    return true;
                });

                if (!r.equals(rules.get(0))) { // don't show this for the first rule as it is meaningless
                    final RadioContainer typeView = (RadioContainer) groupHeader.findComponentById(ResourceTable.Id_rule_type_group);
                    MenuItem showRuleType = menu.add(MeunItemType.NO_SUB, ResourceTable.String_rule_type);
                    showRuleType.setOnMenuItemClickListener(item -> {
                        typeView.setVisibility(Component.VISIBLE);
                        groupHeader.invalidate();
                        return true;
                    });
                    MenuItem moveUp = menu.add(MeunItemType.NO_SUB, ResourceTable.String_move_up);
                    moveUp.setOnMenuItemClickListener(item -> {
                        int current = rules.indexOf(r);
                        if (current < 0) { // not found shouldn't happen
                            return true;
                        }
                        rules.remove(current);
                        rules.add(Math.max(0, current - 1), r);
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0);
                        return true;
                    });
                }
                if (!r.equals(rules.get(rules.size() - 1))) {
                    MenuItem moveDown = menu.add(MeunItemType.NO_SUB, ResourceTable.String_move_down);
                    moveDown.setOnMenuItemClickListener(item -> {
                        int current = rules.indexOf(r);
                        if (current < 0) { // not found shouldn't happen
                            return true;
                        }
                        int size = rules.size();
                        rules.remove(current);
                        rules.add(Math.min(size - 1, current + 1), r);
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0);
                        return true;
                    });
                }
                ll.addComponent(groupHeader);
                // comments
                String comment = r.getComment();
                if (comment != null && !"".equals(comment)) {

                    DirectionalLayout intervalLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_comment, null, false);
                    TextField commentComment = (TextField) intervalLayout.findComponentById(ResourceTable.Id_comment);
                    commentComment.setText(comment);
                    setTextWatcher(commentComment, r::setComment);
                    addStandardMenuItems(intervalLayout, null);
                    ll.addComponent(intervalLayout);
                }
                if (r.isTwentyfourseven()) {
                    DirectionalLayout twentyFourSeven = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_twentyfourseven, null, false);
                    addStandardMenuItems(twentyFourSeven, () -> {
                        r.setTwentyfourseven(false);
                        updateString();
                        watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                    });
                    ll.addComponent(twentyFourSeven);
                }
                // year range list
                final List<YearRange> years = r.getYears();
                if (years != null && !years.isEmpty()) {
                    for (final YearRange yr : years) {
                        addYearRangeUI(ll, r, years, yr);
                    }
                }
                // week range list
                final List<WeekRange> weeks = r.getWeeks();
                if (weeks != null && !weeks.isEmpty()) {
                    for (final WeekRange w : weeks) {
                        addWeekRangeUI(ll, r, weeks, w);
                    }
                }
                // date range list
                final List<DateRange> dateRanges = r.getDates();
                if (dateRanges != null && !dateRanges.isEmpty()) {
                    for (final DateRange dateRange : dateRanges) {
                        addDateRangeUI(ll, r, dateRanges, dateRange);
                    }
                }

                // only used in group mode
                if (groupMode) {
                    first = false;
                }
                headerCount++;
            }

            // days and times will be different per rule
            // are supposedly pseudo days
            // holiday list
            final List<Holiday> holidays = r.getHolidays();
            if (holidays != null && !holidays.isEmpty()) {
                for (final Holiday hd : holidays) {
                    addHolidayUI(ll, r, holidays, hd);
                }
            }
            // week day list
            final List<WeekDayRange> days = r.getDays();
            if (days != null && !days.isEmpty()) {
                addWeekDayUI(ll, days);
            }

            // times
            List<TimeSpan> times = r.getTimes();
            addTimeSpanUIs(ll, times);

            // Modifier
            final RuleModifier rm = r.getModifier();
            if (rm != null) {
                DirectionalLayout modifierLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_modifier, null, false);
                final NiceSpinner modifierSpinner = (NiceSpinner) modifierLayout.findComponentById(ResourceTable.Id_modifier);
                try {

                    String[] values = getResourceManager().getElement(ResourceTable.Strarray_modifier_entries).getStringArray();
                    List<String> resultList = new ArrayList<>(Arrays.asList(values));
                    modifierSpinner.attachDataSource(resultList);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }


                Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_modifier_values, modifierSpinner,
                        rm.getModifier() == null ? "" : rm.getModifier().toString());
                setSpinnerListenerEntryValues(ResourceTable.Strarray_modifier_values, modifierSpinner, rm::setModifier);
                TextField modifierComment = (TextField) modifierLayout.findComponentById(ResourceTable.Id_comment);
                modifierComment.setText(rm.getComment());
                setTextWatcher(modifierComment, rm::setComment);
                addStandardMenuItems(modifierLayout, () -> {
                    r.setModifier(null);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                });
                ll.addComponent(modifierLayout);
            }
        }
    }

    /**
     * Helper class to reduce code duplication when adding date range menu items
     *
     * @author simon
     */
    class DateRangeMenuListener implements OnMenuItemClickListener {
        final DateMenuInterface datesAdder;
        final Rule r;

        /**
         * Construct a new listener for date range menu entries
         *
         * @param r          the Rule
         * @param datesAdder call back to add the date
         */
        DateRangeMenuListener(@NotNull Rule r, @NotNull DateMenuInterface datesAdder) {
            this.r = r;
            this.datesAdder = datesAdder;
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            List<DateRange> mdr = r.getDates();
            if (mdr == null) {
                r.setDates(new ArrayList<>());
                mdr = r.getDates();
            }
            DateRange dateRange = new DateRange();
            datesAdder.addDates(dateRange);
            mdr.add(dateRange);
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0);
            return true;
        }
    }

    /**
     * Create the week day ui
     *
     * @param ll   the DirectionalLayout container
     * @param days a list of WeekDayRanges
     */
    private void addWeekDayUI(@NotNull DirectionalLayout ll, @NotNull final List<WeekDayRange> days) {
        final DirectionalLayout weekDayRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_weekday_range_row, null, false);
        final DependentLayout weekDayContainer = (DependentLayout) weekDayRow.findComponentById(ResourceTable.Id_weekDayContainer);

        Menu menu = addStandardMenuItems(weekDayRow, () -> {
            List<WeekDayRange> temp = new ArrayList<>(days);
            for (WeekDayRange d : temp) {
                if (d.getNths() == null) {
                    days.remove(d);
                }
            }
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
        });
        // menu item will be enabled/disabled depending on number of days etc.
        MenuItem nthMenuItem = menu.add(MeunItemType.NO_SUB, ResourceTable.String_occurrence_in_month);
        nthMenuItem.setOnMenuItemClickListener(item -> {
            DirectionalLayout nthLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_nth, null, false);
            DependentLayout nthContainer = (DependentLayout) nthLayout.findComponentById(ResourceTable.Id_nthContainer);
            setNthListeners(nthContainer, days.get(0));
            weekDayRow.addComponent(nthLayout);
            item.setEnabled(false);
            return true;
        });

        List<WeekDayRange> normal = new ArrayList<>();
        List<WeekDayRange> withNth = new ArrayList<>();
        for (WeekDayRange d : days) {
            if (d.getNths() == null || d.getNths().isEmpty()) {
                normal.add(d);
            } else {
                withNth.add(d);
            }
        }

        if (!normal.isEmpty()) {
            for (WeekDayRange d : normal) {
                WeekDay startDay = d.getStartDay();
                WeekDay endDay = d.getEndDay();
                if (endDay == null) {
                    checkWeekDay(weekDayContainer, startDay.toString());
                } else {
                    int startIndex = startDay.ordinal();
                    int endIndex = endDay.ordinal();
                    if (endIndex < startIndex) { // handle wrap around spec by splitting in two
                        for (int i = startIndex; i <= WeekDay.SU.ordinal(); i++) {
                            checkWeekDay(weekDayContainer, weekDays.get(i));
                        }
                        startIndex = WeekDay.MO.ordinal();
                    }
                    for (int i = startIndex; i <= endIndex; i++) {
                        checkWeekDay(weekDayContainer, weekDays.get(i));
                    }
                }
            }
            setWeekDayListeners(weekDayContainer, days, normal, false, nthMenuItem);
            nthMenuItem.setEnabled(justOneDay(normal));
            ll.addComponent(weekDayRow);
        }

        if (!withNth.isEmpty()) {
            for (final WeekDayRange d : withNth) {
                final DirectionalLayout weekDayRowNth = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_weekday_range_row, null, false);
                final DependentLayout weekDayContainerNth = (DependentLayout) weekDayRowNth.findComponentById(ResourceTable.Id_weekDayContainer);
                WeekDay startDay = d.getStartDay();
                List<Nth> nths = d.getNths();
                checkWeekDay(weekDayContainerNth, startDay.toString());
                nthMenuItem.setEnabled(false);
                DirectionalLayout nthLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_nth, null, false);
                DependentLayout nthContainer = (DependentLayout) nthLayout.findComponentById(ResourceTable.Id_nthContainer);
                for (Nth nth : nths) {
                    int startNth = nth.getStartNth();
                    int endNth = nth.getEndNth();
                    if (endNth < 1) {
                        checkNth(nthContainer, startNth);
                    } else {
                        for (int i = startNth; i <= endNth; i++) {
                            checkNth(nthContainer, i);
                        }
                    }
                }
                List<WeekDayRange> inContainer = new ArrayList<>();
                inContainer.add(d);
                setWeekDayListeners(weekDayContainerNth, days, inContainer, true, nthMenuItem);
                setNthListeners(nthContainer, d);
                weekDayRowNth.addComponent(nthLayout);
                menu = addStandardMenuItems(weekDayRowNth, () -> {
                    days.remove(d);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
                });

                final Component offsetContainer = weekDayRowNth.findComponentById(ResourceTable.Id_offset_container);
                int offsetValue = d.getOffset();
                setOffsetVisibility(offsetValue, menu, offsetContainer);

                TextField offset = (TextField) nthLayout.findComponentById(ResourceTable.Id_offset);
                offset.setText(Integer.toString(offsetValue));
                setTextWatcher(offset, value -> {
                    int o = 0;
                    try {
                        o = Integer.parseInt(value);
                    } catch (NumberFormatException nfex) {
                    }
                    d.setOffset(o);
                });

                ll.addComponent(weekDayRowNth);
            }
        }
    }

    SetDateRangeListener realDateRangeListener = null;

    @Override
    public void setDateRange(int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear, Month endMonth,
                             WeekDay endWeekday, int endDay, VarDate endVarDate) {
        if (realDateRangeListener != null) {

            realDateRangeListener.setDateRange(startYear, startMonth, startWeekday, startDay, startVarDate, endYear, endMonth, endWeekday, endDay, endVarDate);
        }
    }

    /**
     * Add the DateRange UI
     *
     * @param ll         the DirectionalLayout container
     * @param r          the Rule
     * @param dateRanges a List of DateRanges to display holding the range we want to display
     * @param dateRange  the DateRange we want to display
     */
    private void addDateRangeUI(@NotNull DirectionalLayout ll, @NotNull final Rule r, @NotNull final List<DateRange> dateRanges,
                                @NotNull final DateRange dateRange) {
        final DateWithOffset startDate = dateRange.getStartDate();
        final DateWithOffset endDate = dateRange.getEndDate();

        boolean hasStartOffset = startDate.getWeekDayOffset() != null || startDate.getDayOffset() != 0;
        boolean hasEndOffset = endDate != null && (endDate.getWeekDayOffset() != null || endDate.getDayOffset() != 0);
        boolean hasStartOccurrence = startDate.getNthWeekDay() != null;
        boolean hasEndOccurrence = endDate != null && endDate.getNthWeekDay() != null;

        int layoutRes = 0;
        if (hasStartOffset || hasEndOffset) {
            layoutRes = ResourceTable.Layout_daterange_with_offset_row;
        } else if (hasStartOccurrence || hasEndOccurrence) {
            layoutRes = ResourceTable.Layout_daterange_occurrence_row;
        } else {
            layoutRes = ResourceTable.Layout_daterange_row;
        }
        final DirectionalLayout dateRangeRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(layoutRes, null, false);

        // add menus
        final Menu menu = addStandardMenuItems(dateRangeRow, () -> {
            dateRanges.remove(dateRange);
            if (dateRanges.isEmpty()) {
                r.setDates(null);
            }
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
        });

        if (hasStartOffset || hasEndOffset || hasStartOccurrence || hasEndOccurrence) {
            DirectionalLayout startDateLayout = (DirectionalLayout) dateRangeRow.findComponentById(ResourceTable.Id_startDate);
            DirectionalLayout endDateLayout = (DirectionalLayout) dateRangeRow.findComponentById(ResourceTable.Id_endDate);
            setDateRangeValues(startDate, endDate, dateRangeRow, menu);
            startDateLayout.setClickedListener(v -> {
                final DateWithOffset start = startDate;
                if (start.getVarDate() == null && start.getNthWeekDay() == null) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        start.setYear(startYear);
                        start.setMonth(startMonth);
                        start.setDay(startDay);
                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(start, null, finalRow, menu);
                        updateString();
                    };
                    DateRangePicker.showDialog(getContext(), ResourceTable.String_date, start.getYear(), start.getMonth(), start.getDay(), OpeningHoursFragment.this);


                } else if (start.getVarDate() != null && start.getNthWeekDay() == null) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        start.setYear(startYear);
                        start.setVarDate(startVarDate);
                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(start, null, finalRow, menu);
                        updateString();
                    };
                    DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date, start.getYear(), start.getVarDate(), OpeningHoursFragment.this);

                } else if (start.getNthWeekDay() != null) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        start.setYear(startYear);
                        start.setMonth(startMonth);
                        start.setNth(startWeekday, startDay);
                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(start, null, finalRow, menu);
                        updateString();
                    };
                    OccurrenceInMonthPicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date_weekday_occurrence, start.getYear(), start.getMonth(),
                            start.getNthWeekDay(), start.getNth(), OpeningHoursFragment.this);
                } else {
                }
            });
            endDateLayout.setClickedListener(v -> {
                if (endDate == null || (endDate.getVarDate() == null && endDate.getNthWeekDay() == null)) {
                    final DateWithOffset end;
                    if (endDate == null) {
                        end = new DateWithOffset();
                        end.setMonth(Month.JAN);
                    } else {
                        end = endDate;
                    }
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        // we are only using the first NPV thats why we need to use the startXXX values
                        // here
                        DateWithOffset tempDwo = endDate;
                        if (tempDwo == null
                                && (startYear != YearRange.UNDEFINED_YEAR || startMonth != null || startDay != DateWithOffset.UNDEFINED_MONTH_DAY)) {
                            tempDwo = new DateWithOffset();
                            dateRange.setEndDate(tempDwo);
                        }
                        if (tempDwo != null) {
                            tempDwo.setYear(startYear);
                            tempDwo.setMonth(startMonth);
                            tempDwo.setDay(startDay);
                        }
                        end.setYear(startYear);
                        end.setMonth(startMonth);
                        end.setDay(startDay);
                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(startDate, tempDwo, finalRow, menu);
                        updateString();
                        if (endDate == null) {
                            eventHandler.postTask(new Runnable() {
                                @Override
                                public void run() {
                                    watcher.onTextUpdated(null, 0, 0, 0);
                                }
                            }, 100);
                        }
                    };
                    DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date, end.getYear(), end.getMonth(), end.getDay(), OpeningHoursFragment.this);
                } else if (endDate.getVarDate() != null && endDate.getNthWeekDay() == null) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        // we are only using the first NPV thats why we need to use the startXXX values
                        // here
                        endDate.setYear(startYear);
                        endDate.setVarDate(startVarDate);
                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(startDate, endDate, finalRow, menu);
                        updateString();
                    };
                    DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date, endDate.getYear(), endDate.getVarDate(), OpeningHoursFragment.this);
                } else if (endDate.getNthWeekDay() != null) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        endDate.setYear(startYear);
                        endDate.setMonth(startMonth);
                        endDate.setNth(startWeekday, startDay);
                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(startDate, endDate, finalRow, menu);
                        updateString();
                    };
                    OccurrenceInMonthPicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date_weekday_occurrence, endDate.getYear(), endDate.getMonth(),
                            endDate.getNthWeekDay(), endDate.getNth(), OpeningHoursFragment.this);
                } else {
                }
            });
        } else {
            setDateRangeValues(startDate, endDate, dateRangeRow, menu);
            DependentLayout dateRangeLayout = (DependentLayout) dateRangeRow.findComponentById(ResourceTable.Id_daterange_container);
            dateRangeLayout.setClickedListener(v -> {
                int tempEndYear = YearRange.UNDEFINED_YEAR;
                Month tempEndMonth = null;
                int tempEndDay = DateWithOffset.UNDEFINED_MONTH_DAY;

                final DateWithOffset start = startDate;
                DateWithOffset end = endDate;

                if (end != null) {
                    tempEndYear = end.getYear();
                    tempEndMonth = end.getMonth();
                    tempEndDay = end.getDay();
                }
                if (start.getVarDate() == null && (end == null || end.getVarDate() == null)) {
                    if (start.isOpenEnded()) {
                        realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                                 Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                            start.setYear(startYear);
                            start.setMonth(startMonth);
                            start.setDay(startDay);
                            final DirectionalLayout finalRow = dateRangeRow;
                            setDateRangeValues(start, null, finalRow, menu);
                            updateString();
                        };
                        DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date, start.getYear(), start.getMonth(), start.getDay(), OpeningHoursFragment.this);
                    } else {
                        realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                                 Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                            start.setYear(startYear);
                            start.setMonth(startMonth);
                            start.setDay(startDay);
                            DateWithOffset tempDwo = endDate;
                            if (tempDwo == null && (endYear != YearRange.UNDEFINED_YEAR || endMonth != null || endDay != DateWithOffset.UNDEFINED_MONTH_DAY)) {
                                tempDwo = new DateWithOffset();
                                dateRange.setEndDate(tempDwo);
                            }
                            if (tempDwo != null) {
                                tempDwo.setYear(endYear);
                                tempDwo.setMonth(endMonth);
                                tempDwo.setDay(endDay);
                            }
                            final DirectionalLayout finalRow = dateRangeRow;
                            setDateRangeValues(start, tempDwo, finalRow, menu);
                            updateString();
                            if (endDate == null) {
                                // force endDate to be set
                                eventHandler.postTask(new Runnable() {
                                    @Override
                                    public void run() {
                                        watcher.onTextUpdated(null, 0, 0, 0);
                                    }
                                }, 100);
                            }
                        };
                        DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date_range, start.getYear(), start.getMonth(), start.getDay(),
                                tempEndYear, tempEndMonth, tempEndDay, OpeningHoursFragment.this);
                    }
                } else if (start.getVarDate() != null && (end == null || end.getVarDate() == null)) {
                    if (start.isOpenEnded()) {
                        realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                                 Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                            start.setYear(startYear);
                            start.setVarDate(startVarDate);
                            final DirectionalLayout finalRow = dateRangeRow;
                            setDateRangeValues(start, null, finalRow, menu);
                            updateString();
                        };
                        DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date, start.getYear(), start.getVarDate(), OpeningHoursFragment.this);
                    } else {
                        realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                                 Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                            start.setYear(startYear);
                            start.setVarDate(startVarDate);
                            DateWithOffset tempDwo = endDate;
                            if (tempDwo == null && (endYear != YearRange.UNDEFINED_YEAR || endMonth != null || endDay != DateWithOffset.UNDEFINED_MONTH_DAY)) {
                                tempDwo = new DateWithOffset();
                                dateRange.setEndDate(tempDwo);
                            }
                            if (tempDwo != null) {
                                tempDwo.setYear(endYear);
                                tempDwo.setMonth(endMonth);
                                tempDwo.setDay(endDay);
                            }
                            final DirectionalLayout finalRow = dateRangeRow;
                            setDateRangeValues(start, tempDwo, finalRow, menu);
                            updateString();
                            if (endDate == null) {
                                // force endDate to be set
                                eventHandler.postTask(new Runnable() {
                                    @Override
                                    public void run() {
                                        watcher.onTextUpdated(null, 0, 0, 0);
                                    }
                                }, 100);
                            }
                        };
                        DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date_range, start.getYear(), start.getVarDate(), tempEndYear,
                                tempEndMonth, tempEndDay, OpeningHoursFragment.this);
                    }
                } else if (start.getVarDate() == null && (end != null && end.getVarDate() != null)) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        start.setYear(startYear);
                        start.setMonth(startMonth);
                        start.setDay(startDay);

                        endDate.setYear(endYear);
                        endDate.setVarDate(endVarDate);

                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(start, endDate, finalRow, menu);
                        updateString();
                    };
                    DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date_range, start.getYear(), start.getMonth(), start.getDay(), tempEndYear,
                            end.getVarDate(), OpeningHoursFragment.this);
                } else if (start.getVarDate() != null && (end != null && end.getVarDate() != null)) {
                    realDateRangeListener = (int startYear, Month startMonth, WeekDay startWeekday, int startDay, VarDate startVarDate, int endYear,
                                             Month endMonth, WeekDay endWeekday, int endDay, VarDate endVarDate) -> {
                        start.setYear(startYear);
                        start.setVarDate(startVarDate);

                        endDate.setYear(endYear);
                        endDate.setVarDate(endVarDate);

                        final DirectionalLayout finalRow = dateRangeRow;
                        setDateRangeValues(start, endDate, finalRow, menu);
                        updateString();
                    };
                    DateRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_date_range, start.getYear(), start.getVarDate(), tempEndYear,
                            end.getVarDate(), OpeningHoursFragment.this);
                }
            });
        }
        ll.addComponent(dateRangeRow);
    }

    private void setDateRangeValues(@NotNull final DateWithOffset start, @Nullable final DateWithOffset end, @NotNull DirectionalLayout dateRangeRow,
                                    @NotNull Menu menu) {
        Text startYearView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_startYear);
        if (start.getYear() != YearRange.UNDEFINED_YEAR) {
            startYearView.setText(Integer.toString(start.getYear()));
        } else {
            startYearView.setText("");
        }
        Text startDelimiter = (Text) dateRangeRow.findComponentById(ResourceTable.Id_startMonthDayDelimiter);
        Text startMonthView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_startMonth);
        Text startWeekdayView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_startWeekday);
        Text startDayView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_startMonthDay);
        if (start.getVarDate() == null && start.getNthWeekDay() == null) {
            startDelimiter.setVisibility(Component.VISIBLE);
            startDayView.setVisibility(Component.VISIBLE);
            if (startWeekdayView != null) {
                startWeekdayView.setVisibility(Component.HIDE);
            }
            if (start.getMonth() != null) {
                try {
                    String[] values = getResourceManager().getElement(ResourceTable.Strarray_months_entries).getStringArray();
                    startMonthView.setText(values[start.getMonth().ordinal()]);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }

            }

            if (start.getDay() != DateWithOffset.UNDEFINED_MONTH_DAY) {
                startDayView.setText(Integer.toString(start.getDay()));
            } else {
                startDayView.setText("");
            }
        } else if (start.getVarDate() != null && start.getNthWeekDay() == null) {
            startDelimiter.setVisibility(Component.HIDE);
            startDayView.setVisibility(Component.HIDE);
            if (startWeekdayView != null) {
                startWeekdayView.setVisibility(Component.HIDE);
            }
            try {
                String[] values = getResourceManager().getElement(ResourceTable.Strarray_vardate_entries).getStringArray();
                startMonthView.setText(values[start.getVarDate().ordinal()]);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NotExistException e) {
                e.printStackTrace();
            } catch (WrongTypeException e) {
                e.printStackTrace();
            }
        } else if (start.getNthWeekDay() != null) {
            startDelimiter.setVisibility(Component.VISIBLE);
            startDayView.setVisibility(Component.VISIBLE);
            startWeekdayView.setVisibility(Component.VISIBLE);
            if (start.getMonth() != null) {
                try {
                    String[] values = getResourceManager().getElement(ResourceTable.Strarray_months_entries).getStringArray();
                    startMonthView.setText(values[start.getMonth().ordinal()]);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }

            }
            if (start.getNthWeekDay() != null) {
                try {
                    String[] values = getResourceManager().getElement(ResourceTable.Strarray_weekdays_entries).getStringArray();
                    startWeekdayView.setText(values[start.getNthWeekDay().ordinal()]);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }

            }
            if (start.getNth() != DateWithOffset.UNDEFINED_MONTH_DAY) {
                startDayView.setText("[" + Integer.toString(start.getNth()) + "]");
            } else {
                startDayView.setText("");
            }
        } else {
        }
        // offset stuff
        DependentLayout startWeekDayContainer = (DependentLayout) dateRangeRow.findComponentById(ResourceTable.Id_startWeekDayContainer);
        if (startWeekDayContainer != null) {

            checkWeekDay(startWeekDayContainer, start.getWeekDayOffset() != null ? start.getWeekDayOffset().toString() : null);
            setWeekDayListeners(startWeekDayContainer, start);
            final NiceSpinner offsetTypeSpinner = (NiceSpinner) dateRangeRow.findComponentById(ResourceTable.Id_startOffsetType);
            try {

                String[] values = getResourceManager().getElement(ResourceTable.Strarray_offset_type_entries).getStringArray();
                List<String> resultList = new ArrayList<>(Arrays.asList(values));
                offsetTypeSpinner.attachDataSource(resultList);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NotExistException e) {
                e.printStackTrace();
            } catch (WrongTypeException e) {
                e.printStackTrace();
            }
            Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_offset_type_values, offsetTypeSpinner, start.isWeekDayOffsetPositive() ? "+" : "-");
            setSpinnerListenerEntryValues(ResourceTable.Strarray_offset_type_values, offsetTypeSpinner, value -> start.setWeekDayOffsetPositive("+".equals(value)));

            final NiceSpinner endtTypeSpinner = (NiceSpinner) dateRangeRow.findComponentById(ResourceTable.Id_endOffsetType);
            endtTypeSpinner.setAlpha(0.3f);
            try {

                String[] values = getResourceManager().getElement(ResourceTable.Strarray_offset_type_entries).getStringArray();
                List<String> resultList = new ArrayList<>(Arrays.asList(values));
                endtTypeSpinner.attachDataSource(resultList);
                endtTypeSpinner.setSelectedIndex(0);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NotExistException e) {
                e.printStackTrace();
            } catch (WrongTypeException e) {
                e.printStackTrace();
            }
        }
        final DirectionalLayout startOffsetContainer = (DirectionalLayout) dateRangeRow.findComponentById(ResourceTable.Id_start_offset_container);
        if (startOffsetContainer != null) {
            TextField offset = (TextField) startOffsetContainer.findComponentById(ResourceTable.Id_start_offset);
            setTextWatcher(offset, value -> {
                int o = 0;
                try {
                    o = Integer.parseInt(value);
                } catch (NumberFormatException nfex) {
                    // Empty
                }
                start.setDayOffset(o);
            });
            if (start.getDayOffset() != 0) {
                offset.setText(Integer.toString(start.getDayOffset()));
                startOffsetContainer.setVisibility(Component.VISIBLE);
            } else {
                startOffsetContainer.setVisibility(Component.HIDE);
                if (!itemExistsInMenu(menu, ResourceTable.String_show_start_offset)) {
                    MenuItem showOffset = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_start_offset);
                    showOffset.setOnMenuItemClickListener(item -> {
                        startOffsetContainer.setVisibility(Component.VISIBLE);
                        return true;
                    });
                }
            }
        }
        if (end != null) {
            Text yearView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_endYear);
            if (end.getYear() != YearRange.UNDEFINED_YEAR) {
                yearView.setText(Integer.toString(end.getYear()));
            } else {
                yearView.setText("");
            }
            Text endDelimiter = (Text) dateRangeRow.findComponentById(ResourceTable.Id_endMonthDayDelimiter);
            Text endMonthView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_endMonth);
            Text endWeekdayView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_endWeekday);
            Text endDayView = (Text) dateRangeRow.findComponentById(ResourceTable.Id_endMonthDay);
            if (end.getVarDate() == null && end.getNthWeekDay() == null) {
                endDelimiter.setVisibility(Component.VISIBLE);
                endDayView.setVisibility(Component.VISIBLE);
                if (endWeekdayView != null) {
                    endWeekdayView.setVisibility(Component.HIDE);
                }

                if (end.getMonth() != null) {
                    try {
                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_months_entries).getStringArray();
                        endMonthView.setText(values[end.getMonth().ordinal()]);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }

                } else {
                    endMonthView.setText("");
                }
                if (end.getDay() != DateWithOffset.UNDEFINED_MONTH_DAY) {
                    endDayView.setText(Integer.toString(end.getDay()));
                } else {
                    endDayView.setText("");
                }
            } else if (end.getVarDate() != null && end.getNthWeekDay() == null) {
                endDelimiter.setVisibility(Component.HIDE);
                endDayView.setVisibility(Component.HIDE);
                if (endWeekdayView != null) {
                    endWeekdayView.setVisibility(Component.HIDE);
                }
                try {
                    String[] values = getResourceManager().getElement(ResourceTable.Strarray_vardate_entries).getStringArray();
                    endMonthView.setText(values[end.getVarDate().ordinal()]);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }

            } else if (end.getNthWeekDay() != null) {
                endDelimiter.setVisibility(Component.VISIBLE);
                endDayView.setVisibility(Component.VISIBLE);
                endWeekdayView.setVisibility(Component.VISIBLE);
                if (end.getMonth() != null) {
                    try {
                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_months_entries).getStringArray();
                        endMonthView.setText(values[end.getMonth().ordinal()]);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }

                }
                if (end.getNthWeekDay() != null) {
                    try {
                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_weekdays_entries).getStringArray();
                        endWeekdayView.setText(values[end.getNthWeekDay().ordinal()]);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }

                }
                if (end.getNth() != DateWithOffset.UNDEFINED_MONTH_DAY) {
                    endDayView.setText("[" + Integer.toString(end.getNth()) + "]");
                } else {
                    endDayView.setText("");
                }
            } else {
            }
            if ((end.getMonth() != null && end.getDay() != DateWithOffset.UNDEFINED_MONTH_DAY) || end.getVarDate() != null || end.getNthWeekDay() != null) {
                // offset stuff
                setEnableEndDateOffsets(dateRangeRow, true);
                DependentLayout endWeekDayContainer = (DependentLayout) dateRangeRow.findComponentById(ResourceTable.Id_endWeekDayContainer);
                if (endWeekDayContainer != null) {
                    checkWeekDay(endWeekDayContainer, end.getWeekDayOffset() != null ? end.getWeekDayOffset().toString() : null);
                    setWeekDayListeners(endWeekDayContainer, end);
                    final NiceSpinner offsetTypeSpinner = (NiceSpinner) dateRangeRow.findComponentById(ResourceTable.Id_endOffsetType);

                    try {

                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_offset_type_entries).getStringArray();
                        List<String> resultList = new ArrayList<>(Arrays.asList(values));
                        offsetTypeSpinner.attachDataSource(resultList);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }
                    Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_offset_type_values, offsetTypeSpinner, end.isWeekDayOffsetPositive() ? "+" : "-");
                    setSpinnerListenerEntryValues(ResourceTable.Strarray_offset_type_values, offsetTypeSpinner, value -> {
                        end.setWeekDayOffsetPositive("+".equals(value));
                        updateString();
                    });
                }
                final DirectionalLayout endOffsetContainer = (DirectionalLayout) dateRangeRow.findComponentById(ResourceTable.Id_end_offset_container);
                if (endOffsetContainer != null) {
                    TextField offset = (TextField) endOffsetContainer.findComponentById(ResourceTable.Id_end_offset);
                    setTextWatcher(offset, value -> {
                        int o = 0;
                        try {
                            o = Integer.parseInt(value);
                        } catch (NumberFormatException nfex) {
                            // Empty
                        }
                        end.setDayOffset(o);
                        updateString();
                    });
                    if (end.getDayOffset() != 0) {
                        offset.setText(Integer.toString(end.getDayOffset()));
                        endOffsetContainer.setVisibility(Component.VISIBLE);
                    } else {
                        endOffsetContainer.setVisibility(Component.HIDE);
                        if (!itemExistsInMenu(menu, ResourceTable.String_show_end_offset)) {
                            MenuItem showOffset = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_end_offset);
                            showOffset.setOnMenuItemClickListener(item -> {
                                endOffsetContainer.setVisibility(Component.VISIBLE);
                                return true;
                            });
                        }
                    }
                }
            } else {
                setEnableEndDateOffsets(dateRangeRow, false);
            }
        } else {
            if (start.isOpenEnded()) {
                Component endDate = dateRangeRow.findComponentById(ResourceTable.Id_endDate);
                endDate.setVisibility(Component.HIDE);
                Component to = dateRangeRow.findComponentById(ResourceTable.Id_to);
                to.setVisibility(Component.HIDE);
                setVisEndDateOffsets(dateRangeRow, Component.HIDE);
            } else {
                setEnableEndDateOffsets(dateRangeRow, false);
            }
        }
    }

    /**
     * Check if a string resourse is already in use in a specific menu
     *
     * @param menu      menu to check
     * @param stringRes string resource we are interested in
     * @return true if found
     */
    private boolean itemExistsInMenu(Menu menu, int stringRes) {
        for (int i = 0; i < menu.size(); i++) {
            MenuItem mi = menu.getItem(i);
            if (mi.getTitle().equals(getString(stringRes))) {
                return true;
            }
        }
        return false;
    }

    private void setVisEndDateOffsets(@NotNull DirectionalLayout dateRangeRow, int vis) {
        Component endWeekDayContainer = dateRangeRow.findComponentById(ResourceTable.Id_endWeekDayContainer);
        if (endWeekDayContainer != null) {
            endWeekDayContainer.setVisibility(vis);
        }
        Component endOffsetType = dateRangeRow.findComponentById(ResourceTable.Id_endOffsetType);
        if (endOffsetType != null) {
            endOffsetType.setVisibility(vis);
        }
        Component endOffsetContainer = dateRangeRow.findComponentById(ResourceTable.Id_end_offset_container);
        if (endOffsetContainer != null) {
            endOffsetContainer.setVisibility(vis);
        }
    }

    private void setEnableEndDateOffsets(@NotNull DirectionalLayout dateRangeRow, boolean enable) {
        setEnableChlldren(ResourceTable.Id_endWeekDayContainer, dateRangeRow, enable);
        Component endOffsetType = dateRangeRow.findComponentById(ResourceTable.Id_endOffsetType);
        if (endOffsetType != null) {
            endOffsetType.setEnabled(enable);
        }
        setEnableChlldren(ResourceTable.Id_end_offset_container, dateRangeRow, enable);
    }

    private void setEnableChlldren(int res, @NotNull DirectionalLayout dateRangeRow, boolean enable) {
        ComponentContainer container = (ComponentContainer) dateRangeRow.findComponentById(res);

        if (container != null) {
            if (enable) {
                container.setAlpha(1);
            } else {
                container.setAlpha(0.3f);
            }
            int children = container.getChildCount();
            for (int i = 0; i < children; i++) {
                container.getComponentAt(i).setEnabled(enable);
            }
        }
    }

    private void addHolidayUI(@NotNull DirectionalLayout ll, @NotNull final Rule r, @NotNull final List<Holiday> holidays, @NotNull final Holiday hd) {
        DirectionalLayout holidayRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_holiday_row, null, false);
        NiceSpinner holidaysSpinner = (NiceSpinner) holidayRow.findComponentById(ResourceTable.Id_holidays);
        try {

            String[] values = getResourceManager().getElement(ResourceTable.Strarray_holidays_entries).getStringArray();
            List<String> resultList = new ArrayList<>(Arrays.asList(values));
            holidaysSpinner.attachDataSource(resultList);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotExistException e) {
            e.printStackTrace();
        } catch (WrongTypeException e) {
            e.printStackTrace();
        }

        Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_holidays_values, holidaysSpinner, hd.getType().name());
        setSpinnerListenerEntryValues(ResourceTable.Strarray_holidays_values, holidaysSpinner, value -> hd.setType(Type.valueOf(value)));
        Menu menu = addStandardMenuItems(holidayRow, () -> {
            holidays.remove(hd);
            if (holidays.isEmpty()) {
                r.setHolidays(null);
            }
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
        });

        final Checkbox useAsWeekDay = (Checkbox) holidayRow.findComponentById(ResourceTable.Id_checkBoxUseAsWeekDay);
        useAsWeekDay.setChecked(hd.getUseAsWeekDay());
        useAsWeekDay.setCheckedStateChangedListener(new AbsButton.CheckedStateChangedListener() {
            @Override
            public void onCheckedChanged(AbsButton absButton, boolean isChecked) {
                for (Holiday h : holidays) {
                    h.setUseAsWeekDay(isChecked);
                }
                updateString();
                eventHandler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        watcher.onTextUpdated(null, 0, 0, 0);
                    }
                }, 100);
            }
        });


        final Component useAsWeekDayContainer = holidayRow.findComponentById(ResourceTable.Id_useAsWeekDay_container);
        if (!hd.getUseAsWeekDay()) {
            useAsWeekDayContainer.setVisibility(Component.VISIBLE);
        } else {
            useAsWeekDayContainer.setVisibility(Component.HIDE);
            MenuItem showUse = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_useAsWeekDay);
            showUse.setOnMenuItemClickListener(item -> {
                useAsWeekDayContainer.setVisibility(Component.VISIBLE);
                return true;
            });
        }

        final Component offsetContainer = holidayRow.findComponentById(ResourceTable.Id_offset_container);

        int offsetValue = hd.getOffset();
        setOffsetVisibility(offsetValue, menu, offsetContainer);

        TextField offset = (TextField) holidayRow.findComponentById(ResourceTable.Id_offset);
        offset.setText(Integer.toString(offsetValue));
        setTextWatcher(offset, value -> {
            int o = 0;
            try {
                o = Integer.parseInt(value);
            } catch (NumberFormatException nfex) {
                // Empty
            }
            hd.setOffset(o);
        });
        ll.addComponent(holidayRow);
    }

    /**
     * Show or hide the offset field and if hidden add a menu entry
     *
     * @param offset          the current offset value
     * @param menu            the menu to add an entry to
     * @param offsetContainer the container view
     */
    private void setOffsetVisibility(final int offset, @NotNull Menu menu, @NotNull final Component offsetContainer) {
        if (offset != 0) {
            offsetContainer.setVisibility(Component.VISIBLE);
        } else {
            offsetContainer.setVisibility(Component.HIDE);
            MenuItem showOffset = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_offset);
            showOffset.setOnMenuItemClickListener(item -> {
                offsetContainer.setVisibility(Component.VISIBLE);
                return true;
            });
        }
    }

    private void addYearRangeUI(@NotNull DirectionalLayout ll, @NotNull final Rule r, @NotNull final List<YearRange> years, @NotNull final YearRange yr) {
        DirectionalLayout yearLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_year_range, null, false);
        final int startYear = yr.getStartYear();
        final Text startYearView = (Text) yearLayout.findComponentById(ResourceTable.Id_start_year);
        startYearView.setText(Integer.toString(startYear));

        final Text endYearView = (Text) yearLayout.findComponentById(ResourceTable.Id_end_year);
        final int endYear = yr.getEndYear();
        if (endYear > 0) {
            endYearView.setText(Integer.toString(endYear));
        }
        final Component intervalContainer = yearLayout.findComponentById(ResourceTable.Id_interval_container);
        TextField yearIntervalEdit = (TextField) yearLayout.findComponentById(ResourceTable.Id_interval);
        if (yr.getInterval() > 0) {
            intervalContainer.setVisibility(Component.VISIBLE);
            yearIntervalEdit.setText(Integer.toString(yr.getInterval()));
        } else {
            intervalContainer.setVisibility(Component.HIDE);
        }
        setTextWatcher(yearIntervalEdit, value -> {
            int interval = 0;
            try {
                interval = Integer.parseInt(value);
            } catch (NumberFormatException nfex) {
                // Empty
            }
            yr.setInterval(interval);
        });
        Menu menu = addStandardMenuItems(yearLayout, () -> {
            years.remove(yr);
            if (years.isEmpty()) {
                r.setYears(null);
            }
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
        });
        addShowIntervalItem(intervalContainer, menu);

        DirectionalLayout range = (DirectionalLayout) yearLayout.findComponentById(ResourceTable.Id_range);
        range.setClickedListener(v -> {
            int currentYear = Calendar.getInstance().get(Calendar.YEAR);
            int rangeEnd = Math.max(currentYear, startYear) + 50;
            int tempEndYear = yr.getEndYear();
            if (tempEndYear == YearRange.UNDEFINED_YEAR) {
                tempEndYear = RangePicker.NOTHING_SELECTED;
            } else {
                rangeEnd = Math.max(rangeEnd, tempEndYear + 50);
            }
            realSetRangeListener = (int start, int end) -> {
                yr.setStartYear(start);
                startYearView.setText(Integer.toString(start));
                if (end != RangePicker.NOTHING_SELECTED) {
                    endYearView.setText(Integer.toString(end));
                    yr.setEndYear(end);
                } else {
                    endYearView.setText("");
                    yr.setEndYear(YearRange.UNDEFINED_YEAR);
                }
                updateString();
            };
            RangePicker.showDialog(context, ResourceTable.String_year_range, YearRange.FIRST_VALID_YEAR, rangeEnd, yr.getStartYear(), tempEndYear, OpeningHoursFragment.this);


        });
        ll.addComponent(yearLayout);
    }

    /**
     * Add a menu item to show the interval field
     *
     * @param intervalContainer the container view
     * @param menu              the menu we want to add an item to
     */
    private void addShowIntervalItem(@NotNull final Component intervalContainer, @NotNull Menu menu) {
        MenuItem showInterval = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_interval);
        showInterval.setOnMenuItemClickListener(item -> {
            intervalContainer.setVisibility(Component.VISIBLE);
            return true;
        });
    }

    SetRangeListener realSetRangeListener = null;

    @Override
    public void setRange(int start, int end) {
        if (realSetRangeListener != null) {
            realSetRangeListener.setRange(start, end);
        }
    }

    private void addWeekRangeUI(@NotNull DirectionalLayout ll, @NotNull final Rule r, @NotNull final List<WeekRange> weeks, @NotNull final WeekRange w) {
        DirectionalLayout weekLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_week_range, null, false);
        final Text startWeekView = (Text) weekLayout.findComponentById(ResourceTable.Id_start_week);
        final int startWeek = w.getStartWeek();
        startWeekView.setText(Integer.toString(startWeek));

        final Text endWeekView = (Text) weekLayout.findComponentById(ResourceTable.Id_end_week);
        final int endWeek = w.getEndWeek();
        if (endWeek > 0) {
            endWeekView.setText(Integer.toString(endWeek));
        }

        final Component intervalContainer = weekLayout.findComponentById(ResourceTable.Id_interval_container);
        TextField weekIntervalEdit = (TextField) weekLayout.findComponentById(ResourceTable.Id_interval);
        if (w.getInterval() > 0) {
            intervalContainer.setVisibility(Component.VISIBLE);
            weekIntervalEdit.setText(Integer.toString(w.getInterval()));
        } else {
            intervalContainer.setVisibility(Component.HIDE);
        }
        setTextWatcher(weekIntervalEdit, value -> {
            int interval = 0;
            try {
                interval = Integer.parseInt(value);
            } catch (NumberFormatException nfex) {
                // Empty
            }
            w.setInterval(interval);
        });
        Menu menu = addStandardMenuItems(weekLayout, () -> {
            weeks.remove(w);
            if (weeks.isEmpty()) {
                r.setWeeks(null);
            }
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
        });
        addShowIntervalItem(intervalContainer, menu);

        DirectionalLayout range = (DirectionalLayout) weekLayout.findComponentById(ResourceTable.Id_range);

        range.setClickedListener(v -> {
            // need to reget values from w
            int tempEndWeek = w.getEndWeek();
            if (tempEndWeek == WeekRange.UNDEFINED_WEEK) {
                tempEndWeek = RangePicker.NOTHING_SELECTED;
            }
            realSetRangeListener = (int start, int end) -> {
                w.setStartWeek(start);
                startWeekView.setText(Integer.toString(start));
                if (end != RangePicker.NOTHING_SELECTED) {
                    endWeekView.setText(Integer.toString(end));
                    w.setEndWeek(end);
                } else {
                    endWeekView.setText("");
                    w.setEndWeek(WeekRange.UNDEFINED_WEEK);
                }
                updateString();
            };

            RangePicker.showDialog(getContext(), ResourceTable.String_week_range, WeekRange.MIN_WEEK, WeekRange.MAX_WEEK, w.getStartWeek(), tempEndWeek, OpeningHoursFragment.this);
        });
        ll.addComponent(weekLayout);
    }

    /**
     * Check if just one day is in the ranges
     *
     * @param ranges WeekDayRange to check
     * @return return true if just one range and that only contains one day
     */
    private boolean justOneDay(@NotNull List<WeekDayRange> ranges) {
        return ranges.size() == 1 && ranges.get(0).getEndDay() == null;
    }

    /**
     * Set our standard text watcher and a listener on an TextField
     *
     * @param edit     the TextField
     * @param listener listener to call when afterTextChanged is called
     */
    private void setTextWatcher(@NotNull final TextField edit, @NotNull final SetValue listener) {

        edit.addTextObserver(new Text.TextObserver() {
            @Override
            public void onTextUpdated(String s, int i, int i1, int i2) {
                if(edit.getId()!=ResourceTable.Id_comment){
                    if(s.length()>3){
                        edit.setText(s.substring(0,3));
                    }
                }

                listener.set(edit.getText());
                updateString();
            }
        });

    }

    private class DeleteTimeSpan implements Delete {
        final List<TimeSpan> times;
        final TimeSpan ts;

        DeleteTimeSpan(final List<TimeSpan> times, final TimeSpan ts) {
            this.times = times;
            this.ts = ts;
        }

        @Override
        public void delete() {
            times.remove(ts);
            if (times.isEmpty()) {
                // set to null?
            }
            updateString();
            watcher.onTextUpdated(null, 0, 0, 0); // hack to force rebuild of form
        }
    }

    private void addTimeSpanUIs(@NotNull final DirectionalLayout ll, @Nullable final List<TimeSpan> times) {
        if (times != null && !times.isEmpty()) {
            Menu menu = null;
            for (final TimeSpan ts : times) {
                boolean hasStartEvent = ts.getStartEvent() != null;
                boolean hasEndEvent = ts.getEndEvent() != null;
                final boolean extendedTime = ts.getEnd() > 1440;
                boolean hasInterval = ts.getInterval() > 0;
                if (!ts.isOpenEnded() && !hasStartEvent && !hasEndEvent && ts.getEnd() >= 0 && !extendedTime) {
                    DirectionalLayout timeRangeRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_range_row, null, false);
                    RangeBar timeBar = (RangeBar) timeRangeRow.findComponentById(ResourceTable.Id_timebar);

                    int start = ts.getStart();
                    int end = ts.getEnd();
                    if (start >= 360) {
                        timeBar.setTickStart(360f);
                    }
                    setGranuarlty(timeBar, start, end);
                    timeBar.setRangePinsByValue(start, end);
                    timeBar.setPinTextFormatter(timeFormater);
                    timeBar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                        ts.setStart((int) (leftPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        ts.setEnd((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        updateString();
                    });
                    menu = addStandardMenuItems(timeRangeRow, new DeleteTimeSpan(times, ts));
                    addTimePickerMenu(menu, ts, timeBar);
                    addTimeSpanMenus(timeBar, null, menu);

                    ll.addComponent(timeRangeRow);
                } else if (!ts.isOpenEnded() && !hasStartEvent && !hasEndEvent && ts.getEnd() >= 0 && extendedTime) {
                    DirectionalLayout timeExtendedRangeRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_extended_range_row, null, false);
                    RangeBar timeBar = (RangeBar) timeExtendedRangeRow.findComponentById(ResourceTable.Id_timebar);
                    RangeBar extendedTimeBar = (RangeBar) timeExtendedRangeRow.findComponentById(ResourceTable.Id_extendedTimebar);
                    int start = ts.getStart();
                    int end = ts.getEnd();
                    setGranuarlty(timeBar, start, end);
                    setGranuarlty(extendedTimeBar, start, end);
                    timeBar.setRangePinsByValue(0, start);
                    timeBar.setPinTextFormatter(timeFormater);
                    timeBar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                        ts.setStart((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        updateString();
                    });
                    extendedTimeBar.setRangePinsByValue(1440, end);
                    extendedTimeBar.setPinTextFormatter(extendedTimeFormater);
                    extendedTimeBar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                        ts.setEnd((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        updateString();
                    });
                    menu = addStandardMenuItems(timeExtendedRangeRow, new DeleteTimeSpan(times, ts));
                    addTimePickerMenu(menu, ts, timeBar);
                    addTimeSpanMenus(timeBar, extendedTimeBar, menu);
                    ll.addComponent(timeExtendedRangeRow);
                } else if (!ts.isOpenEnded() && !hasStartEvent && !hasEndEvent && ts.getEnd() < 0) {
                    DirectionalLayout timeEventRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_row, null, false);
                    RangeBar timeBar = (RangeBar) timeEventRow.findComponentById(ResourceTable.Id_timebar);
                    timeBar.setPinTextFormatter(timeFormater);
                    int start = ts.getStart();
                    timeBar.setConnectingLineEnabled(false);
                    setGranuarlty(timeBar, start, 0);
                    timeBar.setRangePinsByValue(0, start);
                    timeBar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                        ts.setStart((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        updateString();
                    });
                    menu = addStandardMenuItems(timeEventRow, new DeleteTimeSpan(times, ts));
                    addTimePickerMenu(menu, ts, timeBar);
                    addTimeSpanMenus(timeBar, null, menu);
                    ll.addComponent(timeEventRow);
                } else if (!ts.isOpenEnded() && !hasStartEvent && hasEndEvent) {
                    DirectionalLayout timeEventRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_end_event_row, null, false);
                    RangeBar timeBar = (RangeBar) timeEventRow.findComponentById(ResourceTable.Id_timebar);
                    timeBar.setPinTextFormatter(timeFormater);
                    int start = ts.getStart();
                    setGranuarlty(timeBar, start, 0);
                    timeBar.setRangePinsByValue(0, start);
                    timeBar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                        ts.setStart((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        updateString();
                    });
                    NiceSpinner endEvent = (NiceSpinner) timeEventRow.findComponentById(ResourceTable.Id_endEvent);
                    try {

                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_events_entries).getStringArray();
                        List<String> resultList = new ArrayList<>(Arrays.asList(values));
                        endEvent.attachDataSource(resultList);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }
                    Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_events_values, endEvent, ts.getEndEvent().getEvent().toString());
                    setSpinnerListenerEntryValues(ResourceTable.Strarray_events_values, endEvent, value -> ts.getEndEvent().setEvent(value));
                    menu = addStandardMenuItems(timeEventRow, new DeleteTimeSpan(times, ts));
                    addTimePickerMenu(menu, ts, timeBar);
                    addTimeSpanMenus(timeBar, null, menu);

                    final Component offsetContainer = timeEventRow.findComponentById(ResourceTable.Id_offset_container);
                    int offsetValue = ts.getEndEvent().getOffset();
                    setOffsetVisibility(offsetValue, menu, offsetContainer);

                    TextField offset = (TextField) offsetContainer.findComponentById(ResourceTable.Id_offset);
                    offset.setText(Integer.toString(offsetValue));
                    setTextWatcher(offset, value -> {
                        int o = 0;
                        try {
                            o = Integer.parseInt(value);
                        } catch (NumberFormatException nfex) {
                            // Empty
                        }
                        ts.getEndEvent().setOffset(o);
                    });
                    ll.addComponent(timeEventRow);
                } else if (!ts.isOpenEnded() && hasStartEvent && !hasEndEvent) {
                    DirectionalLayout timeEventRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_start_event_row, null, false);
                    RangeBar timeBar = (RangeBar) timeEventRow.findComponentById(ResourceTable.Id_timebar);
                    RangeBar extendedTimeBar = (RangeBar) timeEventRow.findComponentById(ResourceTable.Id_extendedTimebar);
                    int end = ts.getEnd();
                    if (end != TimeSpan.UNDEFINED_TIME) {
                        int start = 0;
                        RangeBar bar = timeBar;
                        if (extendedTime) {
                            timeBar.setVisibility(Component.HIDE);
                            bar = extendedTimeBar;
                            start = 1440;
                        } else {
                            extendedTimeBar.setVisibility(Component.HIDE);
                            if (end >= 360) {
                                bar.setTickStart(360);
                                start = 360;
                            }
                        }
                        bar.setPinTextFormatter(timeFormater);

                        setGranuarlty(bar, 0, end);
                        bar.setRangePinsByValue(start, end);
                        bar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                            ts.setEnd((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                            updateString();
                        });
                    } else {
                        timeBar.setVisibility(Component.HIDE);
                        extendedTimeBar.setVisibility(Component.HIDE);
                    }
                    NiceSpinner startEvent = (NiceSpinner) timeEventRow.findComponentById(ResourceTable.Id_startEvent);
                    try {

                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_events_entries).getStringArray();
                        List<String> resultList = new ArrayList<>(Arrays.asList(values));
                        startEvent.attachDataSource(resultList);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }

                    Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_events_values, startEvent, ts.getStartEvent().getEvent().toString());
                    setSpinnerListenerEntryValues(ResourceTable.Strarray_events_values, startEvent, value -> ts.getStartEvent().setEvent(value));
                    menu = addStandardMenuItems(timeEventRow, new DeleteTimeSpan(times, ts));
                    if (timeBar.getVisibility() == Component.VISIBLE) {
                        addTimePickerMenu(menu, ts, timeBar);
                        addTimeSpanMenus(timeBar, null, menu);
                    }
                    final Component offsetContainer = timeEventRow.findComponentById(ResourceTable.Id_offset_container);
                    int offsetValue = ts.getStartEvent().getOffset();
                    setOffsetVisibility(offsetValue, menu, offsetContainer);

                    TextField offset = (TextField) offsetContainer.findComponentById(ResourceTable.Id_offset);
                    offset.setText(Integer.toString(offsetValue));
                    setTextWatcher(offset, value -> {
                        int o = 0;
                        try {
                            o = Integer.parseInt(value);
                        } catch (NumberFormatException nfex) {
                            // Empty
                        }
                        ts.getStartEvent().setOffset(o);
                    });
                    ll.addComponent(timeEventRow);
                } else if (!ts.isOpenEnded() && hasStartEvent && hasEndEvent) {
                    DirectionalLayout timeEventRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_event_row, null, false);
                    final NiceSpinner startEvent = (NiceSpinner) timeEventRow.findComponentById(ResourceTable.Id_startEvent);
                    try {

                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_events_entries).getStringArray();
                        List<String> resultList = new ArrayList<>(Arrays.asList(values));
                        startEvent.attachDataSource(resultList);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }
                    Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_events_values, startEvent, ts.getStartEvent().getEvent().toString());
                    setSpinnerListenerEntryValues(ResourceTable.Strarray_events_values, startEvent, value -> ts.getStartEvent().setEvent(value));
                    NiceSpinner endEvent = (NiceSpinner) timeEventRow.findComponentById(ResourceTable.Id_endEvent);
                    try {

                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_events_entries).getStringArray();
                        List<String> resultList = new ArrayList<>(Arrays.asList(values));
                        endEvent.attachDataSource(resultList);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }

                    Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_events_values, endEvent, ts.getEndEvent().getEvent().toString());
                    setSpinnerListenerEntryValues(ResourceTable.Strarray_events_values, endEvent, value -> ts.getEndEvent().setEvent(value));
                    menu = addStandardMenuItems(timeEventRow, new DeleteTimeSpan(times, ts));

                    final Component startOffsetContainer = timeEventRow.findComponentById(ResourceTable.Id_start_offset_container);
                    final Component endOffsetContainer = timeEventRow.findComponentById(ResourceTable.Id_end_offset_container);
                    if (ts.getStartEvent().getOffset() != 0 || ts.getEndEvent().getOffset() != 0) {
                        startOffsetContainer.setVisibility(Component.VISIBLE);
                        endOffsetContainer.setVisibility(Component.VISIBLE);
                    } else {
                        startOffsetContainer.setVisibility(Component.HIDE);
                        endOffsetContainer.setVisibility(Component.HIDE);
                        MenuItem showOffset = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_offset);
                        showOffset.setOnMenuItemClickListener(item -> {
                            startOffsetContainer.setVisibility(Component.VISIBLE);
                            endOffsetContainer.setVisibility(Component.VISIBLE);
                            return true;
                        });
                    }

                    TextField startOffset = (TextField) startOffsetContainer.findComponentById(ResourceTable.Id_start_offset);
                    startOffset.setText(Integer.toString(ts.getStartEvent().getOffset()));
                    setTextWatcher(startOffset, value -> {

                        int offset = 0;
                        try {
                            offset = Integer.parseInt(value);
                        } catch (NumberFormatException nfex) {
                            // Empty
                        }
                        ts.getStartEvent().setOffset(offset);
                    });
                    TextField endOffset = (TextField) endOffsetContainer.findComponentById(ResourceTable.Id_end_offset);
                    endOffset.setText(Integer.toString(ts.getEndEvent().getOffset()));
                    setTextWatcher(endOffset, value -> {
                        int offset = 0;
                        try {
                            offset = Integer.parseInt(value);
                        } catch (NumberFormatException nfex) {
                            // Empty
                        }
                        ts.getEndEvent().setOffset(offset);
                    });
                    ll.addComponent(timeEventRow);
                } else if (ts.isOpenEnded() && !hasStartEvent) {
                    DirectionalLayout timeEventRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_row, null, false);
                    RangeBar timeBar = (RangeBar) timeEventRow.findComponentById(ResourceTable.Id_timebar);
                    timeBar.setPinTextFormatter(timeFormater);
                    int start = ts.getStart();
                    setGranuarlty(timeBar, start, 0);
                    timeBar.setRangePinsByValue(0, start);
                    timeBar.setOnRangeBarChangeListener((rangeBar, leftPinIndex, rightPinIndex, leftPinValue, rightPinValue) -> {
                        ts.setStart((int) (rightPinIndex * rangeBar.getTickInterval() + rangeBar.getTickStart()));
                        updateString();
                    });
                    menu = addStandardMenuItems(timeEventRow, new DeleteTimeSpan(times, ts));
                    addTimePickerMenu(menu, ts, timeBar);
                    addTimeSpanMenus(timeBar, null, menu);
                    ll.addComponent(timeEventRow);
                } else if (ts.isOpenEnded() && hasStartEvent) {
                    DirectionalLayout timeEventRow = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_time_event_row, null, false);
                    final NiceSpinner startEvent = (NiceSpinner) timeEventRow.findComponentById(ResourceTable.Id_startEvent);

                    try {

                        String[] values = getResourceManager().getElement(ResourceTable.Strarray_events_entries).getStringArray();
                        List<String> resultList = new ArrayList<>(Arrays.asList(values));
                        startEvent.attachDataSource(resultList);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NotExistException e) {
                        e.printStackTrace();
                    } catch (WrongTypeException e) {
                        e.printStackTrace();
                    }

                    Util.setSpinnerInitialEntryValue(getResourceManager(), ResourceTable.Strarray_events_values, startEvent, ts.getStartEvent().getEvent().toString());

                    setSpinnerListenerEntryValues(ResourceTable.Strarray_events_values, startEvent, value -> ts.getStartEvent().setEvent(value));
                    NiceSpinner endEvent = (NiceSpinner) timeEventRow.findComponentById(ResourceTable.Id_endEvent);


                    endEvent.setVisibility(Component.HIDE);
                    Component endOffsetContainer = timeEventRow.findComponentById(ResourceTable.Id_end_offset_container);
                    endOffsetContainer.setVisibility(Component.HIDE);
                    menu = addStandardMenuItems(timeEventRow, new DeleteTimeSpan(times, ts));
                    final Component offsetContainer = timeEventRow.findComponentById(ResourceTable.Id_start_offset_container);
                    int offsetValue = ts.getStartEvent().getOffset();
                    setOffsetVisibility(offsetValue, menu, offsetContainer);

                    TextField offset = (TextField) offsetContainer.findComponentById(ResourceTable.Id_start_offset);
                    offset.setText(Integer.toString(offsetValue));
                    setTextWatcher(offset, value -> {
                        int o = 0;
                        try {
                            o = Integer.parseInt(value);
                        } catch (NumberFormatException nfex) {
                            // Empty
                        }
                        ts.getStartEvent().setOffset(o);
                    });
                    ll.addComponent(timeEventRow);
                } else {
                    Text tv = new Text(getContext());
                    tv.setText(ts.toString());
                    ll.addComponent(tv);
                    return;
                }
                final DirectionalLayout intervalLayout = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_interval, null, false);
                TextField intervalEdit = (TextField) intervalLayout.findComponentById(ResourceTable.Id_interval);
                intervalEdit.setText(Integer.toString(ts.getInterval()));
                setTextWatcher(intervalEdit, value -> {
                    int interval = 0;
                    try {
                        interval = Integer.parseInt(value);
                    } catch (NumberFormatException nfex) {
                        // Empty
                    }
                    ts.setInterval(interval);
                });
                ll.addComponent(intervalLayout);
                if (hasInterval) {
                    intervalLayout.setVisibility(Component.VISIBLE);
                } else {
                    intervalLayout.setVisibility(Component.HIDE);
                    MenuItem showInterval = menu.add(MeunItemType.NO_SUB, ResourceTable.String_show_interval);
                    showInterval.setOnMenuItemClickListener(item -> {
                        intervalLayout.setVisibility(Component.VISIBLE);
                        return true;
                    });
                }
            }
        }
    }

    SetTimeRangeListener realSetTimeRangeListener = null;

    @Override
    public void setTimeRange(int startHour, int startMinute, int endHour, int endMinute) {
        if (realSetTimeRangeListener != null) {
            realSetTimeRangeListener.setTimeRange(startHour, startMinute, endHour, endMinute);
        }
    }

    /**
     * Add a menu item that displays a number picker for times
     *
     * @param menu    Menu to and the item to
     * @param ts      TimeSpan to display and modify
     * @param timeBar an optional RangeBar we are associated with
     */
    private void addTimePickerMenu(@NotNull Menu menu, @NotNull final TimeSpan ts, @Nullable final RangeBar timeBar) {
        final MenuItem timePickerMenu = menu.add(MeunItemType.NO_SUB, ResourceTable.String_display_time_picker);
        final OnMenuItemClickListener listener = item -> {
            int start = ts.getStart();
            int end = ts.getEnd();
            int interval = timeBar != null ? (int) timeBar.getTickInterval() : 1;
            if (ts.getStartEvent() == null && ts.getEndEvent() == null && end >= 0) { // t-t, t-x
                realSetTimeRangeListener = (int startHour, int startMinute, int endHour, int endMinute) -> {
                    ts.setStart(startHour * 60 + startMinute);
                    ts.setEnd(endHour * 60 + endMinute);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                };

                TimeRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_time, start / 60, start % 60, end / 60, end % 60, interval, OpeningHoursFragment.this);
            } else if (ts.getStartEvent() == null && (ts.getEndEvent() != null || end < 0)) { // t, t-, t-e
                realSetTimeRangeListener = (int startHour, int startMinute, int endHour, int endMinute) -> {
                    ts.setStart(startHour * 60 + startMinute);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                };
                TimeRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_time, start / 60, start % 60, interval, OpeningHoursFragment.this);
            } else if (ts.getStartEvent() != null && ts.getEndEvent() == null || end >= 0) { // e-t
                realSetTimeRangeListener = (int startHour, int startMinute, int endHour, int endMinute) -> {
                    ts.setEnd(startHour * 60 + startMinute);
                    updateString();
                    watcher.onTextUpdated(null, 0, 0, 0);
                };
                TimeRangePicker.showDialog(OpeningHoursFragment.this, ResourceTable.String_time, start / 60, start % 60, interval, OpeningHoursFragment.this);
            }
            return true;
        };
        timePickerMenu.setOnMenuItemClickListener(listener);
        if (timeBar != null) {
            // lazy hack
            timeBar.setClickedListener(new Component.ClickedListener() {
                @Override
                public void onClick(Component component) {
                    listener.onMenuItemClick(null);
                }
            });
        }
    }

    /**
     * Change the tick interval on a RangeBar
     *
     * @param timeBar  the RangeBar to modify
     * @param interval the new interval
     */
    private void changeTicks(@NotNull final RangeBar timeBar, int interval) {
        double tickInterval = timeBar.getTickInterval();
        float startTick = timeBar.getTickStart();
        int start = (int) (timeBar.getLeftIndex() * tickInterval + startTick);
        start = Math.round(((float) start) / interval) * interval;
        int end = (int) (timeBar.getRightIndex() * tickInterval + startTick);
        end = Math.round(((float) end) / interval) * interval;
        timeBar.setTickInterval(interval);
        timeBar.setRangePinsByValue(start, end);
        timeBar.setVisibleTickInterval(60 / interval);
    }

    private void changeStart(@NotNull final RangeBar timeBar, float newTickStart) {
        double tickInterval = timeBar.getTickInterval();
        float startTick = timeBar.getTickStart();
        int start = (int) (timeBar.getLeftIndex() * tickInterval + startTick);
        int end = (int) (timeBar.getRightIndex() * tickInterval + startTick);
        timeBar.setTickStart(newTickStart);
        timeBar.setRangePinsByValue(start, end);
    }

    private void addTimeSpanMenus(@NotNull final RangeBar timeBar, @Nullable final RangeBar timeBar2, @NotNull Menu menu) {

        final MenuItem item15 = menu.add(MeunItemType.NO_SUB, ResourceTable.String_ticks_15_minute);
        final MenuItem item5 = menu.add(MeunItemType.NO_SUB, ResourceTable.String_ticks_5_minute);
        final MenuItem item1 = menu.add(MeunItemType.NO_SUB, ResourceTable.String_ticks_1_minute);

        item15.setOnMenuItemClickListener(item -> {
            changeTicks(timeBar, 15);
            if (timeBar2 != null) {
                changeTicks(timeBar2, 15);
            }
            item15.setEnabled(false);
            item5.setEnabled(true);
            item1.setEnabled(true);
            return true;
        });

        item5.setOnMenuItemClickListener(item -> {
            changeTicks(timeBar, 5);
            if (timeBar2 != null) {
                changeTicks(timeBar2, 5);
            }
            item15.setEnabled(true);
            item5.setEnabled(false);
            item1.setEnabled(true);
            return true;
        });

        item1.setOnMenuItemClickListener(item -> {
            changeTicks(timeBar, 1);
            if (timeBar2 != null) {
                changeTicks(timeBar2, 1);
            }
            item15.setEnabled(true);
            item5.setEnabled(true);
            item1.setEnabled(false);
            return true;
        });
        item1.setEnabled(false);
        item5.setEnabled(false);
        item15.setEnabled(false);
        if (((int) timeBar.getTickInterval()) != 1) {
            item1.setEnabled(true);
        }
        if (((int) timeBar.getTickInterval()) != 5) {
            item5.setEnabled(true);
        }
        if (((int) timeBar.getTickInterval()) != 15) {
            item15.setEnabled(true);
        }

        if ((int) timeBar.getTickStart() > 0 || (timeBar2 != null && (int) timeBar2.getTickStart() > 0)) {
            final MenuItem expand = menu.add(MeunItemType.NO_SUB, ResourceTable.String_start_at_midnight);
            expand.setOnMenuItemClickListener(item -> {
                changeStart(timeBar, 0f);
                if (timeBar2 != null) {
                    changeStart(timeBar2, 0f);
                }
                expand.setEnabled(false);
                return true;
            });
        }
    }

    private void setGranuarlty(@NotNull RangeBar bar, int start, int end) {
        if ((start % 5 > 0) || (end % 5 > 0)) { // need
            // 1 minute
            // granularity
            bar.setTickInterval(1);
            bar.setVisibleTickInterval(60);
        } else if ((start % 15 > 0) || (end % 15 > 0)) {
            // 5 minute
            // granularity
            bar.setTickInterval(5);
            bar.setVisibleTickInterval(12);
        } else {
            // 15 minute
            // granularity
            bar.setTickInterval(15);
            bar.setVisibleTickInterval(4);
        }
    }

    interface SetValue {
        void set(String value);
    }

    private void setSpinnerListenerEntryValues(final int valuesId, @NotNull final NiceSpinner spinner, @NotNull final SetValue listener) {

        spinner.setOnSpinnerItemSelectedListener(new OnSpinnerItemSelectedListener() {
            @Override
            public void onItemSelected(NiceSpinner niceSpinner, Component component, int i, long l) {
                try {
                    String[] values = getResourceManager().getElement(valuesId).getStringArray();

                    listener.set(values[i]);
                    updateString();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Add the standard menu entries for a Rule
     *
     * @param row      the layout containing the Rule UI
     * @param listener the listener for the deleting the rule
     * @return the created Menu
     */
    private Menu addStandardMenuItems(@NotNull DirectionalLayout row, @Nullable final Delete listener) {
        DirectionalLayout amv = (DirectionalLayout) row.findComponentById(ResourceTable.Id_menu);
        Menu menu = new Menu(context, amv);
        MenuItem mi = menu.add(MeunItemType.NO_SUB, ResourceTable.String_Delete);
        mi.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                if (listener != null) {
                    listener.delete();
                }
                return true;
            }
        });
        return menu;
    }

    /**
     * Runnable that updates the OH string
     */
    Runnable updateStringRunnable = () -> {
        if (rules != null) {
            text.removeTextObserver(watcher);
            String oh = ch.poole.openinghoursparser.Util.rulesToOpeningHoursString(rules);
            text.setText(oh);
            text.addTextObserver(watcher);
            enableSaveButton(oh);
            final int len = oh.length();
            if (len > OSM_MAX_TAG_LENGTH) {
                Util.toastTop(getContext(), getString(ResourceTable.String_value_too_long, len));
            }
        }
    };

    private void sendUpdateStringRunnable() {
        eventHandler.postTask(updateStringRunnable, 100);
    }

    /**
     * Update the actual OH string
     */
    private void updateString() {
        eventHandler.removeAllEvent();
        sendUpdateStringRunnable();
    }

    /**
     * Set a check mark for the day specified
     *
     * @param container layout containing the checkboxes
     * @param day       two letter day as a string
     */
    private void checkWeekDay(@NotNull DependentLayout container, @Nullable String day) {
        for (int i = 0; i < container.getChildCount(); i++) {
            Component v = container.getComponentAt(i);
            if ((v instanceof Checkbox) && ((String) v.getComponentDescription()).equals(day)) {
                ((Checkbox) v).setChecked(true);
                return;
            }
        }
    }

    private void checkNth(@NotNull DependentLayout container, int nth) {
        for (int i = 0; i < container.getChildCount(); i++) {
            Component v = container.getComponentAt(i);
            if (v instanceof Checkbox && ((String) v.getComponentDescription()).equals(Integer.toString(nth))) {
                ((Checkbox) v).setChecked(true);
                return;
            }
        }
    }

    private void setWeekDayListeners(@NotNull final DependentLayout container, @NotNull final List<WeekDayRange> days,
                                     @NotNull final List<WeekDayRange> inContainer, final boolean justOne, @NotNull final MenuItem nthMenuItem) {


        AbsButton.CheckedStateChangedListener listener = new AbsButton.CheckedStateChangedListener() {
            @Override
            public void onCheckedChanged(AbsButton buttonView, boolean isChecked) {
                if (justOne) { // Nth exists
                    if (isChecked) {
                        WeekDayRange range = inContainer.get(0);
                        for (int i = 0; i < container.getChildCount(); i++) {
                            final Component c = container.getComponentAt(i);
                            if (c instanceof Checkbox && !c.equals(buttonView)) {
                                ((Checkbox) c).setChecked(false);
                            }
                        }
                        range.setStartDay((String) buttonView.getComponentDescription());
                    } else { // hack alert
                        for (int i = 0; i < container.getChildCount(); i++) {
                            final Component c = container.getComponentAt(i);
                            if (c instanceof Checkbox) {
                                if (((Checkbox) c).isChecked()) {
                                    return;
                                }
                            }
                        }
                        ((Checkbox) buttonView).setChecked(true);
                    }
                } else {
                    List<WeekDayRange> temp = new ArrayList<>(days);
                    for (WeekDayRange d : temp) {
                        if (d.getNths() == null) {
                            days.remove(d);
                        }
                    }
                    WeekDayRange range = null;
                    for (int i = 0; i < container.getChildCount(); i++) {
                        final Component c = container.getComponentAt(i);
                        if (c instanceof Checkbox) {
                            if (((Checkbox) c).isChecked()) {
                                if (range == null) {
                                    range = new WeekDayRange();
                                    range.setStartDay((String) c.getComponentDescription());
                                    days.add(range);
                                } else {
                                    range.setEndDay((String) c.getComponentDescription());
                                }
                            } else {
                                range = null;
                            }
                        }
                    }
                    nthMenuItem.setEnabled(justOneDay(days));
                }
                updateString();
            }
        };

        for (int i = 0; i < container.getChildCount(); i++) {
            final Component v = container.getComponentAt(i);
            if (v instanceof Checkbox) {
                ((Checkbox) v).setCheckedStateChangedListener(listener);
            }
        }
    }

    private void setWeekDayListeners(@NotNull final DependentLayout container, @NotNull final DateWithOffset dwo) {

        AbsButton.CheckedStateChangedListener listener = new AbsButton.CheckedStateChangedListener() {
            @Override
            public void onCheckedChanged(AbsButton absButton, boolean isChecked) {
                if (isChecked) {
                    for (int i = 0; i < container.getChildCount(); i++) {
                        final Component c = container.getComponentAt(i);

                        if (c instanceof Checkbox && !c.equals(absButton)) {
                            ((Checkbox) c).setChecked(false);
                        }
                    }
                    dwo.setWeekDayOffset(absButton.getComponentDescription().toString());
                } else { // hack alert
                    for (int i = 0; i < container.getChildCount(); i++) {
                        final Component c = container.getComponentAt(i);
                        if ((c instanceof Checkbox)) {
                            if (((Checkbox) c).isChecked()) {
                                return;
                            }
                        }
                    }
                    ((Checkbox) absButton).setChecked(false);
                    dwo.setWeekDayOffset((WeekDay) null);
                }
                updateString();
            }
        };


        for (int i = 0; i < container.getChildCount(); i++) {
            final Component v = container.getComponentAt(i);
            if (v instanceof Checkbox) {
                ((Checkbox) v).setCheckedStateChangedListener(listener);
            }
        }
    }

    private void setNthListeners(@NotNull final DependentLayout container, @NotNull final WeekDayRange days) {

        AbsButton.CheckedStateChangedListener listener = new AbsButton.CheckedStateChangedListener() {
            @Override
            public void onCheckedChanged(AbsButton absButton, boolean b) {
                List<Nth> nths = days.getNths();
                if (nths == null) {
                    nths = new ArrayList<>();
                    days.setNths(nths);
                } else {
                    nths.clear();
                }
                Nth range = null;
                for (int i = 0; i < container.getChildCount(); i++) {
                    final Component c = container.getComponentAt(i);
                    String s = c.getComponentDescription().toString();
                    if (c instanceof Checkbox) {
                        if (((Checkbox) c).isChecked()) {
                            int nth = Integer.parseInt((String) c.getComponentDescription());
                            if (range == null) {
                                range = new Nth();
                                range.setStartNth(nth);
                                nths.add(range);
                            } else { // don't mix pos and neg valus
                                if (Integer.signum(nth) != Integer.signum(range.getStartNth())) {
                                    range = new Nth();
                                    range.setStartNth(nth);
                                    nths.add(range);
                                } else {
                                    range.setEndNth(nth);
                                }
                            }
                        } else {
                            range = null;
                        }
                    }
                }
                updateString();
            }
        };


        for (int i = 0; i < container.getChildCount(); i++) {
            final Component v = container.getComponentAt(i);
            if (v instanceof Checkbox) {
                ((Checkbox) v).setCheckedStateChangedListener(listener);
            }
        }
    }


    @Override
    protected void onActive() {
        super.onActive();
        if (loadedDefault) {
            Util.toastTop(getContext(), getString(ResourceTable.String_loaded_default));
        }
        if ((openingHoursValue == null || "".equals(openingHoursValue)) && showTemplates && !textMode) {
            showTemplates = false;

            showTemplateMangementDialog(false, key, region, object, text.getText());

        }

    }

    private void showTemplateMangementDialog(boolean manage, ValueWithDescription key, String region, String object, String current) {
        TemplateMangementDialog templateMangementDialog = new TemplateMangementDialog(OpeningHoursFragment.this);
        Intent paramIntent = templateMangementDialog.getParamIntent(manage, key, region, object, current);
        present(templateMangementDialog, paramIntent);
    }

    /**
     * Convert density independent pixels to screen pixels
     *
     * @param vp the dip value
     * @return the actual pixels
     */
    private int vpToPixels(int vp) {

        return AttrHelper.vp2px(vp, context);
    }

    /**
     * Enable / disable the save button depending on if the current value is the same as the original one
     *
     * @param oh oh value to test against
     */
    private void enableSaveButton(@Nullable String oh) {
        if (saveButton != null) {
            boolean b = originalOpeningHoursValue == null || (!originalOpeningHoursValue.equals(oh) && (oh == null || oh.length() <= OSM_MAX_TAG_LENGTH));
            saveButton.setClickable(b);
            saveButton.setEnabled(b);
            if (b) {
                ShapeElement shapeElement = new ShapeElement();
                shapeElement.setCornerRadius(2);
                shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.getIntColor("#f5f5f5")));
                saveButton.setBackground(shapeElement);
            } else {
                ShapeElement shapeElement = new ShapeElement();
                shapeElement.setCornerRadius(2);
                shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.getIntColor("#e9e9e9")));
                saveButton.setBackground(shapeElement);
            }
        }
    }

    @Override
    public void updateText(String newText) {
        text.removeTextObserver(watcher);
        text.setText(newText);
        text.addTextObserver(watcher);
        watcher.onTextUpdated(null, 0, 0, 0);
    }
}
